本文面向:使用 Codex CLI、Trae 或 GitHub Copilot 的开发者,想把对话数据导入 ChatCrystal。 预计阅读时间:8 分钟
三个数据源,三种格式
ChatCrystal 支持 5 种 AI 编程工具的对话导入。Claude Code 和 Cursor 在前两篇已经讲过,这篇覆盖剩下的三个:
| 数据源 | 存储格式 | 数据位置 |
|---|---|---|
| Codex CLI | JSONL 事件流 | ~/.codex/sessions/ |
| Trae | SQLite (state.vscdb) | Trae workspaceStorage |
| GitHub Copilot | JSONL / JSON | VS Code workspaceStorage + globalStorage |
它们的格式完全不同,但 ChatCrystal 的插件架构屏蔽了这些差异------导入命令一样:
bash
crystal import
自动检测你装了哪些工具,有数据就导入。
Codex CLI
数据在哪
javascript
~/.codex/sessions/
├── <session-hash>/
│ └── rollout-2026-05-10T14-30-00-abc123.jsonl
├── <session-hash>/
│ └── rollout-2026-05-10T15-00-00-def456.jsonl
└── session_index.jsonl ← 会话名称索引
每个 rollout-*.jsonl 是一次对话,文件名里嵌入了时间戳和会话 ID。
JSONL 事件流格式
Codex CLI 的 JSONL 不是简单的一行一条消息,而是事件流:
json
{"type": "session_meta", "payload": {"id": "abc123", "cwd": "/home/user/project", "git": {"branch": "main"}}}
{"type": "event_msg", "timestamp": "2026-05-10T14:30:01Z", "payload": {"type": "user_message", "message": "帮我优化这个查询"}}
{"type": "event_msg", "timestamp": "2026-05-10T14:30:05Z", "payload": {"type": "agent_message", "message": "分析了你的查询..."}}
{"type": "response_item", "payload": {"type": "message", "role": "assistant", "content": [{"type": "output_text", "text": "分析了你的查询..."}]}}
ChatCrystal 需要从这些事件里重建对话:
| 事件类型 | 用途 | ChatCrystal 处理 |
|---|---|---|
session_meta |
会话元数据 | 提取 sessionId、cwd、git branch |
event_msg + user_message |
用户输入 | 作为 user 消息 |
event_msg + agent_message |
助手回复 | 作为 assistant 消息 |
response_item + message |
完整消息对象 | 检查是否与 agent_message 重复,去重后保留 |
response_item + function_call |
工具调用 | 标记 lastAssistantMsg.hasToolUse = true |
特殊处理:VS Code 上下文剥离
Codex VS Code 插件会在用户消息前面加上 IDE 上下文信息:
markdown
## Environment
- OS: linux
- ...
## My request for Codex:
帮我优化这个查询
ChatCrystal 会自动找到 ## My request for Codex: 标记,只保留后面的实际请求。这样摘要不会被环境信息污染。
session_index.jsonl
Codex CLI 会维护一个会话名称索引:
json
{"id": "abc123", "thread_name": "优化数据库查询"}
ChatCrystal 读取这个文件,把 thread_name 作为对话的 slug(标题),这样导入后更容易识别。
Trae
数据在哪
| 系统 | 路径 |
|---|---|
| Windows | %APPDATA%\Trae\User\workspaceStorage\ |
| macOS | ~/Library/Application Support/Trae/User/workspaceStorage/ |
| Linux | ~/.config/Trae/User/workspaceStorage/ |
和 Cursor 一样,数据存在 SQLite 的 state.vscdb 里,但存储结构不同。
存储 key
Trae 把所有对话存在一个 key 下:
sql
SELECT value FROM ItemTable WHERE [key] = 'memento/icube-ai-agent-storage'
这个 value 是一个 JSON 对象:
json
{
"list": [
{
"sessionId": "session-001",
"createdAt": 1715340600000,
"updatedAt": 1715340900000,
"messages": [
{
"role": "user",
"content": "帮我搭一个 React 项目",
"turnIndex": 0
},
{
"role": "assistant",
"content": "",
"agentTaskContent": {
"proposal": "我来帮你创建一个 React 项目...",
"guideline": {
"planItems": [
{"toolName": "create_file", "thought": "创建 package.json"},
{"toolName": "finish", "thought": "项目创建完成,已配置好..."}
]
}
}
}
]
}
],
"currentSessionId": "session-001"
}
助手消息提取
Trae 的 Agent(SOLO Builder)回复比较特殊。助手消息的 content 字段经常是空的,实际内容藏在 agentTaskContent 里。ChatCrystal 的提取优先级:
msg.content--- 直接内容,优先使用agentTaskContent.proposal--- 提案文本agentTaskContent.guideline.planItems里toolName === "finish"的thought--- 最终总结
如果 content 为空,ChatCrystal 会从 agentTaskContent 里拼接出完整回复。
思考过程提取
Trae 的思考过程分散在多个地方:
agentTaskContent.proposalReasoningContent--- 顶层推理agentTaskContent.guideline.planItems[].reasoningContent--- 每个步骤的推理
ChatCrystal 会把所有推理内容合并为 thinking 字段。
GitHub Copilot
数据在哪
Copilot 的对话数据分布在两个位置:
工作区对话(有项目上下文):
bash
~/.config/Code/User/workspaceStorage/<hash>/chatSessions/
├── session-001.jsonl
├── session-002.json
└── ...
全局对话(无项目,如新窗口聊天):
bash
~/.config/Code/User/globalStorage/emptyWindowChatSessions/
├── session-003.jsonl
└── ...
| 系统 | VS Code 路径 |
|---|---|
| Windows | %APPDATA%\Code\User\ |
| macOS | ~/Library/Application Support/Code/User/ |
| Linux | ~/.config/Code/User/ |
两种文件格式
Copilot 同时使用 .jsonl 和 .json 两种格式:
.jsonl 格式(第一行是快照,后续行是 UI 状态补丁):
css
行1: {"kind": 0, "v": {"sessionId": "...", "requests": [...]}} ← 只读这一行
行2: {"kind": 1, ...} ← UI 补丁,跳过
行3: {"kind": 1, ...}
.json 格式(旧版,整个文件是一个 JSON 对象):
json
{
"sessionId": "abc123",
"creationDate": 1715340600000,
"customTitle": "React 项目搭建",
"requests": [...]
}
ChatCrystal 对 .jsonl 只读第一行(kind: 0 的快照),跳过后续的 UI 状态补丁。
请求-响应结构
每个 request 包含用户消息和助手回复:
json
{
"requestId": "req-001",
"timestamp": 1715340600000,
"message": {
"text": "怎么用 React Query 做缓存"
},
"response": [
{"kind": "thinking", "value": "用户想了解 React Query 的缓存机制..."},
{"kind": "text", "value": "React Query 的缓存基于 stale-while-revalidate 策略..."},
{"kind": "toolInvocationSerialized", ...}
]
}
response 数组里每项的 kind 决定类型:
| kind | 说明 | ChatCrystal 处理 |
|---|---|---|
text |
文本回复 | 提取为 content |
thinking |
思考过程 | 提取为 thinking |
toolInvocationSerialized |
工具调用 | 标记 hasToolUse |
mcpServersStarting |
MCP 启动信息 | 跳过 |
inlineReference |
内联引用 | 跳过 |
undefined |
无 kind 的文本 | 提取为 content(兼容旧版) |
自定义标题
Copilot 允许用户给对话设置标题(customTitle),ChatCrystal 会把它作为 slug 导入,方便在界面上识别。
自定义数据目录
如果某个工具的数据不在默认位置,通过环境变量指定:
bash
# Codex CLI
CODEX_SESSIONS_DIR=/path/to/codex/sessions crystal import
# Trae
TRAE_DATA_DIR=/path/to/trae/User crystal import
# GitHub Copilot
COPILOT_DATA_DIR=/path/to/code/User crystal import
或在 ChatCrystal 设置页面修改对应配置。
导入验证
导入完成后,检查各数据源的状态:
bash
crystal status
输出示例:
yaml
ChatCrystal Status
Server : running
Database : connected
Conversations: 147
Sources : claude-code(52), cursor(38), codex(21), copilot(19), trae(17)
Notes : 89
Tags : 23
如果某个数据源显示 0,检查:
- 对应工具是否有历史对话
- 数据目录路径是否正确
- 运行
crystal import --source <数据源名>单独触发某个数据源
下一步
- Claude Code 对话自动导入完全指南 --- 了解 JSONL 格式和噪音过滤
- Cursor 对话导入:解析 SQLite 里的宝藏 --- 深入了解 SQLite 格式解析