---
title: 导入 Cursor 对话：工作区元数据与全局消息
description: 核对 ChatCrystal 如何关联 Cursor 的两类 SQLite 数据、发现孤立会话和提取消息，并用真实会话验证导入范围。
image: "https://zengliangyi.com/og/blog/cursor-conversation-import.png"
---

Cursor 对话导入不能只寻找一个聊天文本文件。当前 ChatCrystal 适配器从工作区取得会话线索，再从全局数据库读取消息。核对这两个位置是否属于同一份用户数据，比仅看“导入完成”更有意义。

本文基于 [ChatCrystal](/projects/chatcrystal) 提交 [`7642768`](https://github.com/ZengLiangYi/ChatCrystal/tree/764276814e5c5a33ecbfeae63754f74b9261a3a8) 的 [Cursor 适配器](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/server/src/parser/adapters/cursor.ts)。这是应用对已知存储结构的处理方式，不是 Cursor 承诺稳定的公开导出格式。

## 定位同一个 User 目录

适配器使用的默认基础目录如下：

| 平台 | 基础目录 |

| --- | --- |

| Windows | `%APPDATA%\Cursor\User` |

| macOS | `~/Library/Application Support/Cursor/User` |

| Linux | `~/.config/Cursor/User` |

`CURSOR_DATA_DIR` 可以覆盖基础目录，值应指向 `User` 层级，不是某个工作区哈希目录。与其他本机来源相同，变量由应用服务在启动时读取；在已有服务的客户端导入命令前设置它，不会改变那个服务的配置。



```text
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 提取文本和辅助字段。

下面是说明字段的构造示例，不是一次真实导入的原始记录：



```json
{  
    "_v"  :   3  ,  
    "type"  :   1  ,  
    "text"  :   "为什么这个请求会重复执行？"  ,  
    "createdAt"  :   "2026-10-01T10:00:00Z"  
  }
```



当前代码将 `type === 1` 视为用户，其余值走助手分支，并跳过没有正文的助手 Bubble。思考文本另存，工具与代码字段用于设置标志；它不保证完整保存所有工具输出或编辑器状态。

`_v` 大于 3 时，代码会发出版本警告后继续按已知字段解析。这不证明新格式已经完整兼容；应检查实际消息内容，不能把警告统一当作无害信息。

## 孤立会话仍有发现边界

适配器还寻找全局库中不在已知工作区 Composer 列表里的会话。这样可以发现部分工作区已移除、消息仍留下的历史，但它不是无遗漏恢复工具。

当前候选检查只查看每个会话最多 20 个 Bubble，确认其中是否含有非空文本；这个 SQL 查询没有排序。候选检查未看到文本、全局库无法打开或 key 形状变化，都可能影响发现结果。发现候选之后，实际解析再读取该会话匹配的全部 Bubble。

[数据库读取工具](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/server/src/parser/vscdb.ts)把主数据库文件读入 sql.js 内存实例，失败时短暂等待后重试一次。它没有把读取失败、文件损坏和格式不支持精确分类，也没有在这里重放 SQLite WAL；读取不到记录不能直接证明 Cursor 已删除历史。

## 用一个真实会话验收

在默认本机连接下执行：



```bash
crystal   import  
  crystal   --json   conversations   --source   cursor   --limit   20
```



第二条命令返回包含 `items` 的对象。选择自己知道内容的真实会话，核对 ID、项目与消息数，再通过应用详情或 `GET /api/conversations/<id>` 查看实际消息。CLI 的导入来源选项有本机分支限制，详见 [Claude Code 导入](/blog/claude-code-conversation-import)，不要用它承诺只扫描 Cursor。

至少验证三种情况：正常工作区、只有全局消息的历史、已知包含格式异常或空助手消息的文件。关注实际保留的内容，不用未经测量的“几十条需要几秒”判断成功。

## 更新历史后重新核对

导入服务用文件元数据和解析内容哈希决定跳过或替换。Cursor 当前扫描中，已知工作区会话的 `fileMtime` 优先取 Composer 创建时间，`fileSize` 取全局库大小。因此，不能保证每次内容变化都会仅凭元数据检查被识别，尤其是全局文件大小未变时。

测试一个已导入会话的更新，确认新的消息确实进入应用；否则保留会话 ID、文件状态和对应版本作为排查证据。笔记还需要经过摘要与向量处理，导入记录的变化不自动证明旧笔记仍然正确。首次配置与单条笔记的验收见[入门流程](/blog/chatcrystal-getting-started)。

分享：

##  相关文章

[##  解析 Cursor SQLite：从表与键排查缺失消息

沿 Cursor 适配器检查两个数据库、键前缀、未知版本和孤立会话，区分读不到、解析遗漏与真正没有记录。          曾亮懿       2026年10月3日      阅读约 4 分钟

#ChatCrystal

#Cursor

#SQLite

#数据解析](/blog/cursor-sqlite-parser-pitfalls)    [##  AI 对话格式差异：事件、快照与跨库消息定位

对照五个固定适配器，区分 JSONL 容器、事件语义与 SQLite 键布局，用两个内存数据库观察消息索引、孤立数据和未知版本。          曾亮懿       2026年10月7日      阅读约 4 分钟

#ChatCrystal

#Node.js

#SQLite](/blog/ai-conversation-format-divergence)    [##  AI 对话管理方案：四种存储路径与迁移验收

比较 Claude Code、Cursor、Copilot、Trae 的固定适配器输入，区分原始副本、可读导出与知识库，并用合成清单验证身份和信息保留。          曾亮懿       2026年10月7日      阅读约 4 分钟

#ChatCrystal

#Node.js

#SQLite](/blog/ai-conversation-management-options)

##  从文章到代码

到我的 GitHub 看看 ChatCrystal、开发者工具和日常学习记录，欢迎交流实现细节。

[访问我的 GitHub](https://github.com/ZengLiangYi)

```json
{
  "@context": "https://schema.org",
  "@type": "WebSite",
  "name": "ZengLiangYi Blog",
  "alternateName": [
    "Zeng Liangyi",
    "ZengLiangYi",
    "zengliangyi.com"
  ],
  "url": "https://zengliangyi.com",
  "description": "曾亮懿的个人网站。记录 AI Native 探索、开发者工具与全栈产品实践。"
}
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "导入 Cursor 对话：工作区元数据与全局消息",
  "description": "核对 ChatCrystal 如何关联 Cursor 的两类 SQLite 数据、发现孤立会话和提取消息，并用真实会话验证导入范围。",
  "url": "https://zengliangyi.com/blog/cursor-conversation-import/",
  "image": "https://zengliangyi.com/images/blog/cursor-conversation-import.svg",
  "datePublished": "2026-10-03T00:00:00.000Z",
  "dateModified": "2026-10-03T00:00:00.000Z",
  "author": {
    "@type": "Person",
    "name": "曾亮懿",
    "url": "https://zengliangyi.com/about"
  },
  "publisher": {
    "@type": "Organization",
    "name": "ZengLiangYi Blog",
    "logo": {
      "@type": "ImageObject",
      "url": "https://zengliangyi.com/favicon-96x96.png"
    }
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://zengliangyi.com/blog/cursor-conversation-import/"
  }
}
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "首页",
      "item": "https://zengliangyi.com/"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "文章",
      "item": "https://zengliangyi.com/blog"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "导入 Cursor 对话：工作区元数据与全局消息",
      "item": "https://zengliangyi.com/blog/cursor-conversation-import/"
    }
  ]
}
```
