跳到正文
曾亮懿 ZengLiangYi Blog

解析 Cursor SQLite:从表与键排查缺失消息

沿 Cursor 适配器检查两个数据库、键前缀、未知版本和孤立会话,区分读不到、解析遗漏与真正没有记录。

曾亮懿

曾亮懿

阅读约 4 分钟
目录 展开 收起

导入缺少消息时,先找出是哪一层没有得到预期数据:文件读取、表查询、会话键匹配,还是字段提取。空结果不能直接解释为“源工具删除了记录”,版本警告也不能直接忽略。

本文按 ChatCrystal 的 Cursor 适配器 提交 7642768 解释排查方法。安装、目录和完整导入步骤见 Cursor 导入指南;这里关注解析器的诊断与覆盖边界。

确认文件属于同一份用户数据

工作区数据库提供 Composer 元数据,全局数据库提供 bubble 消息。适配器还使用 workspace.json 获取项目目录。只复制其中一份,或混入另一台机器的全局数据库,都可能导致会话 ID 找不到消息。

数据库读取函数 把主文件读入 sql.js 内存实例,失败后等待 500 毫秒重试一次,再失败返回 null。这不是精确的锁、权限、损坏分类,也不是 WAL 重放。

SQLite 的 WAL 说明解释了已提交修改可能仍在日志文件中。调查实时数据库时,需要一致的数据库视图;可参考 官方备份机制。不要把单独读取主文件的结果当作所有最新记录的证明,或在原数据库上试验性删除文件。

查询正确的表和键

先在测试副本上检查表,再针对适配器认识的键查询。下面的会话 ID 是构造示例:

-- 工作区数据库
SELECT value FROM ItemTable
WHERE [key] = 'composer.composerData';

-- 全局数据库
SELECT [key], value FROM cursorDiskKV
WHERE [key] LIKE 'bubbleId:sample-session:%';

缺少 cursorDiskKV 与该表存在但没有匹配行,是不同的失败。会话键的形状是 bubbleId:<composerId>:<bubbleId>,要查整个会话需要匹配前缀;一个已知完整键也可以用等值查询,不能说这种存储“只能用 LIKE”。

自己实现查询时,应绑定参数,另行处理 LIKE 中 %、_ 的通配语义。当前适配器的查询使用字符串插入,不能把这条建议写成应用已经实现的保护。常规 UUID 不含这些通配字符,也不代表任意异常 ID 都能安全地按同一方式处理。

版本与角色不是完整 schema 校验

_v > 3 时,代码发出警告并继续读取已知字段;它没有证明新版本结构完全兼容。构造 _v: 4、字段改名、text 类型错误的样本,检查是否留下正确内容及能否定位错误。

当前代码把 type === 1 作为用户,其余值作为助手,并跳过无正文的助手 bubble。这是实现规则,不是对所有未知类型的正确分类。空正文也不能单凭这一字段就断定是流式中间态。

thinking 的保留有前提

当 allThinkingBlocks 为非空数组时,代码收集块中的 thinking 或 text;否则可回退到顶层 thinking。但助手 bubble 如果没有正文,会在提取 thinking 之前被跳过。只有推理而没有正文的助手记录,因此可能不在导入消息中。

分别测试正文加推理、只有顶层推理、非空块数组却无可用文本等情况。多格式支持应描述为这些已知路径,不能扩展为“所有思考过程都能完整保留”。

孤立会话检查是采样

全局扫描用键提取 Composer ID,排除已知工作区会话,再检查候选的最多 20 个 bubble 是否有非空文本。bubbleId: 前缀共 9 个字符,因此 SQL 的 SUBSTR([key], 10) 从 ID 开始。

这个候选检查没有排序,也不会检查该会话所有 bubble。把可用文本放在前 20 条之后,是重要的失败样本;不能把 LIMIT 20 的优化解释成不损失覆盖率。候选被发现后,解析阶段才读取全部匹配行。

比较更新后的记录,而不只看数量

工作区路径去前缀并 URL 解码后用于项目显示。当前转换会把 file:///tmp/project 变为 tmp/project,丢失 POSIX 根斜杠,不能保证保留可用的绝对路径。坏编码可导致工作区跳过;目录哈希不是项目名称的可靠替代。还要注意增量导入使用的元数据:已知 Composer 的创建时间可能被用作 fileMtime,配合全局文件大小不能保证识别每次内容变化。

准备一个有已知消息的会话,修改测试副本中的内容后再次导入,比较实际消息是否更新。记录文件、表、键、字段、版本和导入 ID,才能把“没找到”收敛为具体问题。通过解析检查后,再核对生成的笔记是否保留关键条件;相关的事件过滤方法见 Claude Code JSONL 过滤。

分享:

相关文章

导入 Cursor 对话:工作区元数据与全局消息

核对 ChatCrystal 如何关联 Cursor 的两类 SQLite 数据、发现孤立会话和提取消息,并用真实会话验证导入范围。

曾亮懿 曾亮懿
阅读约 4 分钟
#ChatCrystal #Cursor #SQLite #数据解析

AI 对话格式差异:事件、快照与跨库消息定位

对照五个固定适配器,区分 JSONL 容器、事件语义与 SQLite 键布局,用两个内存数据库观察消息索引、孤立数据和未知版本。

曾亮懿 曾亮懿
阅读约 4 分钟
#ChatCrystal #Node.js #SQLite

AI 对话管理方案:四种存储路径与迁移验收

比较 Claude Code、Cursor、Copilot、Trae 的固定适配器输入,区分原始副本、可读导出与知识库,并用合成清单验证身份和信息保留。

曾亮懿 曾亮懿
阅读约 4 分钟
#ChatCrystal #Node.js #SQLite

从文章到代码

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