广告位联系
返回顶部
分享到

Claude Code恢复session对话完整历史的实现

Ai 来源:互联网 作者:佚名 发布时间:2026-06-22 22:41:57 人浏览
摘要

buildConversationChain 如何从 JSONL 原样恢复对话 基于 src/utils/sessionStorage.ts 源码分析。 总体流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 JSONL 文件在磁盘上(无序追加的行) │ ▼ loadTranscript

buildConversationChain— 如何从 JSONL 原样恢复对话

基于 src/utils/sessionStorage.ts 源码分析。

总体流程

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

JSONL 文件在磁盘上(无序追加的行)

        │

        ▼

loadTranscriptFile()                     ← 步骤 1: 解析

        │

        ├─ 逐行 parse JSON → Entry 对象

        ├─ TranscriptMessage → Map<UUID, TranscriptMessage>

        ├─ 元数据条目 → 各自的 Map(summaries, titles, tags...)

        ├─ 计算 leafUuids(哪些消息是叶子节点)

        └─ applyPreservedSegmentRelinks() / applySnipRemovals()

        │

        ▼

findLatestMessage(leafUuids)             ← 步骤 2: 找锚点

        │

        ▼

buildConversationChain(messages, leaf)   ← 步骤 3: 链表回溯

        │

        ▼

recoverOrphanedParallelToolResults()     ← 步骤 4: 修复并行工具

        │

        ▼

返回 TranscriptMessage[](从根到叶的有序数组)

步骤 1:loadTranscriptFile()— 解析 JSONL,建立 Map

位置: src/utils/sessionStorage.ts:3472

1a. 逐行读取

1

const entries = parseJSONL<Entry>(buf)   // 逐行 JSON.parse

每一行 JSON 被解析为一个 Entry 联合类型。解析结果分发到不同的数据结构中。

1b. 分发到不同容器

1

2

3

4

5

6

7

8

const messages = new Map<UUID, TranscriptMessage>()   // 对话消息

const summaries = new Map<UUID, string>()              // compact 摘要

const customTitles = new Map<UUID, string>()           // 用户自定义标题

const tags = new Map<UUID, string>()                   // 标签

const fileHistorySnapshots = new Map<UUID, ...>()      // 文件快照

const attributionSnapshots = new Map<UUID, ...>()      // 归属快照

const contentReplacements = new Map<UUID, ...>()       // 内容替换记录

// ... 更多

对于每一条解析出的 entry 按 type 字段分发:

1

2

3

4

5

6

7

8

9

entry.type === 'user'        → messages.set(entry.uuid, entry)

entry.type === 'assistant'   → messages.set(entry.uuid, entry)

entry.type === 'system'      → messages.set(entry.uuid, entry)

entry.type === 'attachment'  → messages.set(entry.uuid, entry)

entry.type === 'summary'     → summaries.set(entry.leafUuid, entry.summary)

entry.type === 'custom-title'→ customTitles.set(entry.sessionId, entry.customTitle)

entry.type === 'tag'         → tags.set(entry.sessionId, entry.tag)

entry.type === 'mode'        → modes.set(entry.sessionId, entry.mode)

... 以此类推

关键点: JSONL 文件中的行是物理追加顺序,不是逻辑对话顺序。解析后的 Map<UUID, TranscriptMessage> 是一个无序的哈希表——所有消息平铺在一个大 Map 中,逻辑顺序完全由 parentUuid 指针维护。

1c. 处理 legacy progress 桥接

在旧版本中,progress 类型的条目也参与 parentUuid 链。新版中 progress 不再属于 TranscriptMessage。为了兼容旧 transcript,需要桥接:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

// src/utils/sessionStorage.ts:3629-3641

if (isLegacyProgressEntry(entry)) {

  const parent = entry.parentUuid

  progressBridge.set(

    entry.uuid,

    parent && progressBridge.has(parent)

      ? (progressBridge.get(parent) ?? null)  // 链式解析

      : parent,

  )

  continue

}

// 后续处理 TranscriptMessage 时跳过 progress,直连到真正的 parent:

if (entry.parentUuid && progressBridge.has(entry.parentUuid)) {

  entry.parentUuid = progressBridge.get(entry.parentUuid) ?? null

}

1d. 大文件优化

对于超过 SKIP_PRECOMPACT_THRESHOLD 的大 transcript,加载时有三个优化:

  1. readTranscriptForLoad() — 在 fd 层面跳过 compact boundary 之前的字节(attribution-snapshot 行在读取时就被过滤掉)
  2. scanPreBoundaryMetadata() — 从 boundary 之前的字节中恢复 session 元数据(agentSetting、mode、pr-link 等)
  3. walkChainBeforeParse() — 在 JSON 解析之前先遍历 parentUuid 链,丢弃不在链上的死分支(对分叉/合并场景可节省大量内存)

1e. 计算叶子节点

1

2

3

4

5

6

7

8

9

10

11

12

13

// 所有作为别人 parent 的 UUID

const parentUuids = new Set(

  allMessages

    .map(msg => msg.parentUuid)

    .filter((uuid): uuid is UUID => uuid !== null),

)

// 叶子 = 所有消息的 UUID - 所有作为别人 parent 的 UUID

const leafUuids = new Set(

  allMessages

    .filter(m => isUserOrAssistantMessage(m))

    .map(m => m.uuid)

    .filter(uuid => !parentUuids.has(uuid))

)

叶子节点就是没有被任何其他消息引用为 parentUuid 的消息。一个对话可以有多条链(比如分叉),因此有多个叶子。

1f.applyPreservedSegmentRelinks()

位置: src/utils/sessionStorage.ts:1839

当 compact 发生后,JSONL 中有一个 compact boundary 标记。boundary 之前的消息被"保留段"(preserved segment)机制保存:

1

2

3

4

5

6

7

8

9

JSONL 物理布局:

  msg_1 (parent: null)     ← pre-compact

  msg_2 (parent: 1)        ← pre-compact

  msg_3 (parent: 2)        ← pre-compact, preserved segment HEAD

  msg_4 (parent: 3)        ← pre-compact, preserved segment TAIL

  [compact boundary]       ← 带有 preservedSegment 元数据

  msg_5 (parent: null)     ← post-compact: boundary marker

  msg_6 (parent: 5)        ← post-compact: summary

  msg_7 (parent: 6)        ← post-compact: 新用户消息

applyPreservedSegmentRelinks 做四件事:

  1. 重新链接保留段头部 — 将 preserved segment 第一条消息的 parentUuid 指向 boundary 的 anchorUuid
  2. 重新链接保留段尾部 — 将 anchor 的其他直接子消息的 parentUuid 改为指向保留段的尾部
  3. 清零保留段内 assistant 消息的 token usage — 防止 resume 后因旧 usage 数据立即触发 auto-compact
  4. 剪枝 — 删除 boundary 之前且不在保留段内的所有消息

1g.applySnipRemovals()

删除被 snip 操作标记为移除的消息(snip 是一种部分上下文裁剪机制,按消息范围精确删除)。

步骤 2:findLatestMessage()— 找到最新的叶子

位置: src/utils/sessionStorage.ts:2046

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

function findLatestMessage<T extends { timestamp: string }>(

  messages: Iterable<T>,

  predicate: (m: T) => boolean,

): T | undefined {

  let latest: T | undefined

  let maxTime = -Infinity

  for (const m of messages) {

    if (!predicate(m)) continue

    const t = Date.parse(m.timestamp)

    if (t > maxTime) {

      maxTime = t

      latest = m

    }

  }

  return latest

}

从所有叶子节点中,选 timestamp 最新的那个作为恢复的起点。这保证了 --resume 总是恢复到最后一条消息所在的链。

1

2

3

4

5

6

7

8

Map 中所有消息:

  msg_A (parent: null,  ts: 04:20:00)

  msg_B (parent: A,     ts: 04:21:00)

  msg_C (parent: B,     ts: 04:22:00)   ← 被 msg_D 引用为 parent → 不是叶子

  msg_D (parent: B,     ts: 04:23:00)   ← 无人引用为 parent → 是叶子!

 

leafUuids = { msg_D }

findLatestMessage → msg_D(timestamp 最大)

步骤 3:buildConversationChain()— 沿链表回溯

位置: src/utils/sessionStorage.ts:2069

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

export function buildConversationChain(

  messages: Map<UUID, TranscriptMessage>,   // 步骤 1 构建的 HashMap

  leafMessage: TranscriptMessage,           // 步骤 2 选出的最新叶子

): TranscriptMessage[] {

  const transcript: TranscriptMessage[] = []

  const seen = new Set<UUID>()

  let currentMsg: TranscriptMessage | undefined = leafMessage

  // 从叶子向根遍历

  while (currentMsg) {

    // 环检测:防御性编程

    if (seen.has(currentMsg.uuid)) {

      logError(new Error(

        `Cycle detected in parentUuid chain at message ${currentMsg.uuid}. ` +

        `Returning partial transcript.`

      ))

      logEvent('tengu_chain_parent_cycle', {})

      break

    }

    seen.add(currentMsg.uuid)

    // 尾插到数组(结果是倒序的)

    transcript.push(currentMsg)

    // 通过 parentUuid 在 HashMap 中 O(1) 查找上一条消息

    currentMsg = currentMsg.parentUuid

      ? messages.get(currentMsg.parentUuid)

      : undefined

  }

  // 反转数组:从倒序变为正序(根 → 叶)

  transcript.reverse()

  // 修复并行工具调用的孤儿结果

  return recoverOrphanedParallelToolResults(messages, transcript, seen)

}

回溯过程示意

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

JSONL Map(无序 HashMap):

  ┌──────────────────────────────────────────────────────┐

  │ uuid: "D"  type: assistant  parentUuid: "C"  ts:...  │

  │ uuid: "A"  type: user       parentUuid: null  ts:... │

  │ uuid: "C"  type: user       parentUuid: "B"  ts:... │

  │ uuid: "B"  type: assistant  parentUuid: "A"  ts:... │

  └──────────────────────────────────────────────────────┘

 

叶子 = "D"(最新 timestamp,无子节点)

 

回溯:

  step 1: currentMsg = D

           transcript = [D]

  step 2: currentMsg = messages.get("C") = C

           transcript = [D, C]

  step 3: currentMsg = messages.get("B") = B

           transcript = [D, C, B]

  step 4: currentMsg = messages.get("A") = A

           transcript = [D, C, B, A]

  step 5: currentMsg = messages.get(null) = undefined → 停止

 

reverse → [A, B, C, D]  ← 正确的对话顺序(从第一条到最新)

三个关键机制

机制 说明
O(1) HashMap 查找 messages.get(parentUuid) 不依赖 JSONL 中行的物理顺序
环检测 如果链表出现环(数据损坏),记录错误并退出,返回部分链
不完整链自动终止 如果 parentUuid 指向的 UUID 不在 Map 中,get() 返回 undefined,遍历自动终止

步骤 4:recoverOrphanedParallelToolResults()— 恢复并行工具的孤儿结果

位置: src/utils/sessionStorage.ts:2118

这是恢复过程中最精妙的部分。要理解它,必须先理解两个问题:

  1. 当 LLM 发出并行工具调用时,JSONL 里到底写了什么?
  2. buildConversationChain 回溯后,为什么有些消息丢了?

4.1 前置知识:并行工具调用的 JSONL 写入

假设 LLM 在一次回复中同时发出 3 个工具调用:Bash、Read、Grep。

1

2

3

4

5

6

7

API 返回的 assistant 消息(uuid: B):

  message.content = [

    { type: "text",    text: "Let me check..." },

    { type: "tool_use", id: "toolu_1", name: "Bash", ... },

    { type: "tool_use", id: "toolu_2", name: "Read", ... },

    { type: "tool_use", id: "toolu_3", name: "Grep", ... },

  ]

这三个工具并行执行,各自独立完成。每个工具完成后,结果被包装为一个 user 消息写入 JSONL(通过 recordTranscript → insertMessageChain)。

关键在于 insertMessageChain 写入时 parentUuid 是如何分配的。看代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

// src/utils/sessionStorage.ts:1001-1068(简化)

let parentUuid: UUID | null = startingParentUuid ?? null  // 初始值

for (const message of messages) {

  // ★ 关键:对 tool_result 消息,使用 sourceToolAssistantUUID

  let effectiveParentUuid = parentUuid

  if (message.type === 'user' && message.sourceToolAssistantUUID) {

    effectiveParentUuid = message.sourceToolAssistantUUID  // ← 指向 assistant B!

  }

  const transcriptMessage = {

    parentUuid: effectiveParentUuid,  // ← 写入 JSONL 的 parentUuid

    ...message,

  }

  await this.appendEntry(transcriptMessage)

  if (isChainParticipant(message)) {

    parentUuid = message.uuid  // ← 更新顺序链指针,供下一条消息使用

  }

}

每个 tool_result 的 parentUuid 都指向同一个 assistant 消息 B(因为 sourceToolAssistantUUID 覆盖了 effectiveParentUuid)。但是顺序链指针 parentUuid 会在每写入一条消息后更新。

所以 JSONL 中实际写入的内容是:

1

2

3

4

5

6

7

8

消息      type          JSONL 中的 parentUuid    原因

──────────────────────────────────────────────────────────────────

A        user          null                     用户的第一条输入

B        assistant     A                        正常链(B 的 parent 是 A)

C        user          B           ←★            Bash 结果,sourceToolAssistantUUID = B

D        user          B           ←★            Read 结果,sourceToolAssistantUUID = B

E        user          B           ←★            Grep 结果,sourceToolAssistantUUID = B

F        assistant     E           ←★            顺序链:F 的 effectiveParentUuid = parentUuid 变量 = E

关键洞察:

  • C、D、E 的 parentUuid 都是 B —— 它们都是 B 的"子节点"
  • F 的 parentUuid 是 E —— 因为写入 E 之后 parentUuid 变量被更新为 E

4.2 问题:回溯时发生了什么

当 buildConversationChain 从叶子 F 开始回溯:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

从叶子 F 出发,沿 parentUuid 链回溯:

 

  currentMsg = F

  → transcript = [F]

  → 下一个 = messages.get(F.parentUuid) = messages.get("E") = E

 

  currentMsg = E

  → transcript = [F, E]

  → 下一个 = messages.get(E.parentUuid) = messages.get("B") = B

                                                          ↑

                                          注意:E.parentUuid 是 B,不是 D!

 

  currentMsg = B

  → transcript = [F, E, B]

  → 下一个 = messages.get(B.parentUuid) = messages.get("A") = A

 

  currentMsg = A

  → transcript = [F, E, B, A]

  → 下一个 = messages.get(A.parentUuid) = messages.get(null) = undefined → 停止

 

reverse → [A, B, E, F]

问题暴露: 回溯得到的链是 [A, B, E, F],C 和 D 丢了!

1

2

3

4

5

6

7

8

9

10

JSONL Map 中有 6 条 TranscriptMessage:

  A ←── 在链上

  B ←── 在链上

  C ←── ★ 不在链上!parentUuid = B,但回溯走的是 B→E→F

  D ←── ★ 不在链上!parentUuid = B,但回溯走的是 B→E→F

  E ←── 在链上

  F ←── 在链上(叶子)

 

回溯利用的 seen 集合 = {A, B, E, F}

不在 seen 中的 = {C, D}  ← 这就是"孤儿"

为什么会这样?因为 parentUuid 只能表达一对多的"一"那侧。消息 B 有三个子节点(C、D、E),但每个子节点只能有一个 parentUuid 值。回溯时只能沿着一条路径走(B→E→F),其他路径(B→C、B→D)被遗漏了。

4.3 恢复算法详解

recoverOrphanedParallelToolResults 的目的就是找回这些孤儿。

第一步:收集链上所有的 assistant 消息

1

2

const chainAssistants = chain.filter(m => m.type === 'assistant')

// 结果: [B, F]  (链上的两条 assistant)

第二步:建立反向索引 —— 哪些 tool_result 指向这个 assistant

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

// 遍历 Map 中 ALL 消息(不仅是链上的)

// 按 parentUuid 对 tool_result 消息建立索引

const toolResultsByAsst = new Map<UUID, TranscriptMessage[]>()

for (const m of messages.values()) {

  if (m.type === 'user' && m.parentUuid &&

      Array.isArray(m.message.content) &&

      m.message.content.some(b => b.type === 'tool_result')) {

    // m 是一条 tool_result 消息

    const group = toolResultsByAsst.get(m.parentUuid)

    if (group) group.push(m)

    else toolResultsByAsst.set(m.parentUuid, [m])

  }

}

// 遍历后的 toolResultsByAsst:

//   B → [C, D, E]   ← 三条 tool_result 都指向 B!

//   (F 没有 tool_result 子节点,所以不在 Map 中)

第三步:对每个链上的 assistant,找出孤儿

1

2

3

4

5

6

7

8

9

10

11

12

13

14

for (const asst of chainAssistants) {    // asst = B, 然后 asst = F

  // 查找所有 parentUuid 指向该 assistant 的 tool_result

  const trs = toolResultsByAsst.get(asst.uuid)  // B → [C, D, E]

  // 过滤出不在链上的(即 seen 集合中没有的)

  for (const tr of trs) {

    if (!seen.has(tr.uuid)) orphanedTRs.push(tr)

  }

}

// 对于 B: seen = {A, B, E, F}

//   C 不在 seen → 孤儿!

//   D 不在 seen → 孤儿!

//   E 在 seen → 不是孤儿(链上已有)

// orphanedTRs = [C, D]

// 对于 F: toolResultsByAsst 中没有 key=F → 无操作

第四步:按时间戳排序孤儿

1

2

3

orphanedTRs.sort((a, b) => a.timestamp.localeCompare(b.timestamp))

// C 先完成 (ts: 04:20:01), D 后完成 (ts: 04:20:02)

// sorted = [C, D]

第五步:将孤儿插入到正确位置

1

2

3

4

5

// "锚点" = 触发这些 tool_use 的 assistant 消息本身

// 孤儿应该出现在 assistant 和下一个 assistant 之间

const anchor = asst    // 即 B

inserts.set(anchor.uuid, [C, D])

// 含义:在 B 后 面插入 [C, D]

最后:重建数组

1

2

3

4

5

6

7

8

9

10

11

12

const result = []

for (const m of chain) {   // chain = [A, B, E, F]

  result.push(m)

  const toInsert = inserts.get(m.uuid)

  if (toInsert) result.push(...toInsert)

}

// 遍历过程:

//   m = A: result = [A],         A 没有待插入项

//   m = B: result = [A, B],      B 有待插入项 → push(C, D)

//          result = [A, B, C, D]

//   m = E: result = [A, B, C, D, E],  E 没有待插入项

//   m = F: result = [A, B, C, D, E, F], F 没有待插入项

4.4 最终效果对比

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

回溯得到的链(修复前):

  [A, B, E, F]

   ↑  ↑     ↑

   │  │     └─ 第二轮 assistant

   │  └─────── 只有 Grep 的 tool_result(最后完成的那个)

   └────────── 用户输入

 

修复后的链:

  [A, B, C, D, E, F]

   ↑  ↑  ↑  ↑  ↑  ↑

   │  │  │  │  │  └─ 第二轮 assistant

   │  │  │  │  └──── Grep 结果(最后完成)

   │  │  │  └─────── Read 结果(恢复的孤儿)

   │  │  └────────── Bash 结果(恢复的孤儿)

   │  └───────────── 包含 Bash+Read+Grep 的 assistant

   └──────────────── 用户输入

4.5 算法的通用性

这个算法不仅处理单个 assistant 发出多个 tool_use 的情况,还处理:

场景 示例 恢复方式
单个 assistant 发出 N 个并行工具 B 发出 Bash+Read+Grep 恢复 N-1 个孤儿 tool_result
多个 assistant 各自发出工具 B1 发出 Bash, B2 发出 Read 每轮独立恢复
同一 API 请求的多个分片(streaming) B 被分成 B?、B? 两个消息 siblingsByMsgId 按 message.id 分组处理
嵌套工具调用 Tool A 触发 Agent B,Agent B 触发 Tool C 每层 independent 恢复

4.6 一句话总结

并行工具调用的结果在 JSONL 中都指向同一个 assistant 消息作为 parent。但 parentUuid 链是线性的——从叶子回溯只能走到最后完成的那条 tool_result。recoverOrphanedParallelToolResults 通过反向索引(parentUuid → tool_result 列表)找到所有孤儿,按时间戳排序后插回正确位置。

步骤 5: Compact 恢复的特殊处理

preservedSegment 机制

当发生 compact 时,大部分旧消息被摘要替代,但最近几条核心对话可以标记为"保留段"保留在 JSONL 中。

applyPreservedSegmentRelinks

位置: src/utils/sessionStorage.ts:1839

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

恢复前的物理 JSONL:

  msg_X (parent: W)       ← pre-compact

  msg_Y (parent: X)       ← pre-compact

  msg_3 (parent: Y)       ← pre-compact, preserved HEAD

  msg_4 (parent: 3)       ← pre-compact, preserved TAIL

  ═══════════════════════   compact boundary

  boundary_msg (parent: null)  ← post-compact: boundary marker

  summary (parent: boundary)   ← post-compact: 摘要

  new_msg (parent: summary)    ← post-compact: 新对话

applyPreservedSegmentRelinks 后:

  1. msg_3.parentUuid = boundary.anchorUuid   ← 链接保留段头部

  2. anchor 的其他子消息.parentUuid = msg_4.uuid  ← 锚定保留段尾部

  3. 删除 msg_X, msg_Y(不在保留段内)        ← 剪枝

  4. msg_3, msg_4 的 assistant 的 token usage 清零  ← 防止虚假 autocompact

恢复后的逻辑链:

  ... → [boundary → summary] → [msg_3 → msg_4] → [new_msg → ...]

applySnipRemovals

Snip 是一种精确的上下文裁剪操作(按消息 UUID 范围删除)。恢复时:

  • 识别被 snip 标记的消息
  • 从 Map 中移除它们
  • 重新链接受影响的 parentUuid

完整恢复流水线

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

                 磁盘上的 JSONL 文件

                (按时间追加,无序行)

                         │

     ┌───────────────────┼───────────────────┐

     ▼                   ▼                   ▼

逐行 JSON.parse     分类到各容器         计算 leafUuids

Entry 对象          Map<UUID, Msg>     (无子节点的消息 UUID)

     │                   │                   │

     └───────────────────┼───────────────────┘

                         │

                         ▼

               findLatestMessage()

              选 timestamp 最大的叶子

                         │

                         ▼

             buildConversationChain()

            叶子 → parentUuid 回溯 → 根

             环检测 + reverse 反转

                         │

                         ▼

       recoverOrphanedParallelToolResults()

            恢复并行工具的孤儿结果

              插入到正确链位置

                         │

                         ▼

       applyPreservedSegmentRelinks()

            重新链接 compact 保留段

              清零旧 token usage

                         │

                         ▼

       applySnipRemovals()

            删除 snip 标记的消息

                         │

                         ▼

             TranscriptMessage[]

         [msg_1, msg_2, ..., msg_N]

           从根到叶,完整有序的对话

核心设计思想

单向链表 + HashMap 索引 = 简单而强大的持久化方案

  1. 写入简单: 只需追加行,无需维护任何 B-tree 或索引结构
  2. 恢复高效: parentUuid 指针 + HashMap 实现 O(1) 跳转,从叶子走到根即完成恢复
  3. 容错性好: 环检测、不完整链自动终止、legacy progress 桥接
  4. 并行安全: recoverOrphanedParallelToolResults 修复并行工具调用导致的链断裂
  5. 内存优化: 大文件场景下的逐块读取、死分支跳过、fd 级过滤

关键源文件索引

文件 函数 作用
src/utils/sessionStorage.ts:3472 loadTranscriptFile() 解析 JSONL,构建 Map 和元数据
src/utils/sessionStorage.ts:3818 loadSessionFile() 定位 session JSONL 文件并调用 loadTranscriptFile
src/utils/sessionStorage.ts:3842 getSessionMessages() 获取 session 所有消息 UUID(用于去重)
src/utils/sessionStorage.ts:2046 findLatestMessage() 从叶子中选 timestamp 最新的
src/utils/sessionStorage.ts:2069 buildConversationChain() 核心:从叶子沿 parentUuid 回溯到根
src/utils/sessionStorage.ts:2118 recoverOrphanedParallelToolResults() 修复并行工具调用的孤儿结果
src/utils/sessionStorage.ts:1839 applyPreservedSegmentRelinks() 重新链接 compact 保留段
src/utils/sessionStorage.ts:2294 loadTranscriptFromFile() 完整的文件→LogOption 转换
src/utils/conversationRecovery.ts:416 loadMessagesFromJsonlPath() 从 JSONL 路径加载消息的入口之一
src/types/logs.ts:221 TranscriptMessage 对话消息类型定义(含 parentUuid 字段)
src/types/logs.ts:297 Entry 所有 JSONL 条目类型的联合类型

版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 :
相关文章
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计