涵盖:摘要存储与 REPL 展示、JSONL 文件大小管理
4. compact 摘要存储与 REPL 展示
摘要存在哪里
摘要不独立存储。CompactionResult包含 summaryMessages,通过 buildPostCompactMessages 组装后直接成为消息列表的一部分:
// compact.ts:330-337
const postCompactMessages = [
boundaryMarker, // SystemCompactBoundaryMessage
...summaryMessages, // 摘要(isCompactSummary, isVisibleInTranscriptOnly)
...messagesToKeep, // 保留的最近消息
...attachments,
...hookResults,
]
这些消息被:
- yield 到 QueryEngine(
query.ts:530-532) - 持久化到 JSONL transcript(
recordTranscript) - 设置为新的
messagesForQuery(发给 API 的消息列表)
是否替换原消息列表
替换的是 messagesForQuery(发给 API 的消息),不直接删除 state.messages 中的原始消息。
压缩前 messagesForQuery:
[msg0][msg1][msg2]...[msgN-1][msgN]
压缩后 messagesForQuery:
[boundary][summary][msgN-1][msgN]
↑ ↑
被摘要替换 保留的消息
(不再发给 API)
每次 query loop 迭代开始时:
// query.ts:365
let messagesForQuery = [...getMessagesAfterCompactBoundary(messages)]
从最后一个 compact boundary 之后取消息,之前的原始消息不再出现在 API 请求中。
是否影响 REPL 显示
不影响用户看到的聊天记录。 两个机制保证:
机制 1:摘要消息对用户不可见
// sessionMemoryCompact.ts:476-482
const summaryMessages = [createUserMessage({
content: summaryContent,
isCompactSummary: true,
isVisibleInTranscriptOnly: true, // ← 关键
})]
// messages.ts:4675
if (message.isVisibleInTranscriptOnly && !isTranscriptMode) return false
机制 2:REPL 消息列表在 compact 时被裁剪
收到 compact boundary 时:
// REPL.tsx:2594-2599
if (isCompactBoundaryMessage(newMessage)) {
if (isFullscreenEnvEnabled()) {
// 全屏:保留从上一个 boundary 开始的消息(含旧摘要、保留消息等)
setMessages(old => [...getMessagesAfterCompactBoundary(old, { includeSnipped: true }), newMessage])
} else {
// 终端:直接替换为 boundary(旧消息全部丢弃)
setMessages(() => [newMessage])
}
}
总结:各视角的可见性
| 视角 | 能看到什么 |
|---|---|
| API 请求(messagesForQuery) | boundary + 摘要 + 保留消息 |
| REPL 用户窗口 | 原始消息列表(摘要被 isVisibleInTranscriptOnly 过滤) |
| JSONL 转录文件 | 全部历史(boundary 前后的消息都在) |
5. JSONL 转录文件大小管理
没有大小限制
JSONL 文件(~/.claude/projects/<project>/sessions/<sessionId>.jsonl)没有硬性大小上限。每次 compact 只追加写入,不物理截断文件:
// sessionStorage.ts:1432-1439
await getProject().insertMessageChain(newMessages, ...)
读取时的性能优化
当文件超过 5MB(SKIP_PRECOMPACT_THRESHOLD),readTranscriptForLoad 在加载时跳过 compact boundary 之前的旧内容:
// sessionStoragePortable.ts:636-643
if (boundaryAt >= lineStart && ...) {
const hit = parseBoundaryLine(...)
if (hit) {
s.out.len = 0 // 清空缓冲区,丢弃 boundary 前的内容
s.boundaryStartOffset = s.bufFileOff + lineStart
}
}
但这只影响加载性能,不影响文件大小。旧数据仍留在磁盘上。
实际观测
代码注释中记录的实际文件大小:
// sessionStorage.ts:3234-3235
// 41 MB, 99% dead: parseJSONL 56.0 ms -> 3.9 ms (-93%)
// 151 MB, 92% dead: 47.3 ms -> 9.4 ms (-80%)
跨多天重度使用的会话文件可达到 150MB+,其中 90%+ 是 compact boundary 之前的废弃内容。
清理机制
| 方式 | 说明 |
|---|---|
| 物理截断 | 无自动截断机制 |
| 手动删除 | 用户手动删除 sessions/ 下旧 .jsonl 文件 |
/resume 列表限制 | limit 参数控制显示数量(如最近 20 个),但不删除文件 |
| Tombstone 保护 | 50MB 上限(MAX_TOMBSTONE_REWRITE_BYTES),超过后跳过重写 |
取舍
这是有意为之的设计——保留完整历史用于恢复和调试,以磁盘空间换取数据安全。如果磁盘空间成为问题,用户需要手动清理。