最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Claude Code恢復(fù)session對話完整歷史的實現(xiàn)步驟

  發(fā)布時間:2026-06-22 11:01:09   作者:緩步前行的微塵   我要評論
本文主要介紹了Claude Code恢復(fù)session對話完整歷史的實現(xiàn)步驟,涵蓋從磁盤讀取JSONL文件到構(gòu)建無序Map結(jié)構(gòu),到通過parentUuid回溯構(gòu)建有序?qū)υ掓?以及處理并行工具調(diào)用和compact機制的恢復(fù)邏輯,下面就來詳細的了解一下

buildConversationChain— 如何從 JSONL 原樣恢復(fù)對話

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

總體流程

JSONL 文件在磁盤上(無序追加的行)
        │
        ▼
loadTranscriptFile()                     ← 步驟 1: 解析
        │
        ├─ 逐行 parse JSON → Entry 對象
        ├─ TranscriptMessage → Map<UUID, TranscriptMessage>
        ├─ 元數(shù)據(jù)條目 → 各自的 Map(summaries, titles, tags...)
        ├─ 計算 leafUuids(哪些消息是葉子節(jié)點)
        └─ applyPreservedSegmentRelinks() / applySnipRemovals()
        │
        ▼
findLatestMessage(leafUuids)             ← 步驟 2: 找錨點
        │
        ▼
buildConversationChain(messages, leaf)   ← 步驟 3: 鏈表回溯
        │
        ▼
recoverOrphanedParallelToolResults()     ← 步驟 4: 修復(fù)并行工具
        │
        ▼
返回 TranscriptMessage[](從根到葉的有序數(shù)組)

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

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

1a. 逐行讀取

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

每一行 JSON 被解析為一個 Entry 聯(lián)合類型。解析結(jié)果分發(fā)到不同的數(shù)據(jù)結(jié)構(gòu)中。

1b. 分發(fā)到不同容器

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, ...>()       // 內(nèi)容替換記錄
// ... 更多

對于每一條解析出的 entry 按 type 字段分發(fā):

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)
... 以此類推

關(guān)鍵點: JSONL 文件中的行是物理追加順序,不是邏輯對話順序。解析后的 Map<UUID, TranscriptMessage> 是一個無序的哈希表——所有消息平鋪在一個大 Map 中,邏輯順序完全由 parentUuid 指針維護。

1c. 處理 legacy progress 橋接

在舊版本中,progress 類型的條目也參與 parentUuid 鏈。新版中 progress 不再屬于 TranscriptMessage。為了兼容舊 transcript,需要橋接:

// 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
}
// 后續(xù)處理 TranscriptMessage 時跳過 progress,直連到真正的 parent:
if (entry.parentUuid && progressBridge.has(entry.parentUuid)) {
  entry.parentUuid = progressBridge.get(entry.parentUuid) ?? null
}

1d. 大文件優(yōu)化

對于超過 SKIP_PRECOMPACT_THRESHOLD 的大 transcript,加載時有三個優(yōu)化:

  1. readTranscriptForLoad() — 在 fd 層面跳過 compact boundary 之前的字節(jié)(attribution-snapshot 行在讀取時就被過濾掉)
  2. scanPreBoundaryMetadata() — 從 boundary 之前的字節(jié)中恢復(fù) session 元數(shù)據(jù)(agentSetting、mode、pr-link 等)
  3. walkChainBeforeParse() — 在 JSON 解析之前先遍歷 parentUuid 鏈,丟棄不在鏈上的死分支(對分叉/合并場景可節(jié)省大量內(nèi)存)

1e. 計算葉子節(jié)點

// 所有作為別人 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))
)

葉子節(jié)點就是沒有被任何其他消息引用為 parentUuid 的消息。一個對話可以有多條鏈(比如分叉),因此有多個葉子。

1f.applyPreservedSegmentRelinks()

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

當(dāng) compact 發(fā)生后,JSONL 中有一個 compact boundary 標記。boundary 之前的消息被"保留段"(preserved segment)機制保存:

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 元數(shù)據(jù)
  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. 清零保留段內(nèi) assistant 消息的 token usage — 防止 resume 后因舊 usage 數(shù)據(jù)立即觸發(fā) auto-compact
  4. 剪枝 — 刪除 boundary 之前且不在保留段內(nèi)的所有消息

1g.applySnipRemovals()

刪除被 snip 操作標記為移除的消息(snip 是一種部分上下文裁剪機制,按消息范圍精確刪除)。

步驟 2:findLatestMessage()— 找到最新的葉子

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

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
}

從所有葉子節(jié)點中,選 timestamp 最新的那個作為恢復(fù)的起點。這保證了 --resume 總是恢復(fù)到最后一條消息所在的鏈。

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

export function buildConversationChain(
  messages: Map<UUID, TranscriptMessage>,   // 步驟 1 構(gòu)建的 HashMap
  leafMessage: TranscriptMessage,           // 步驟 2 選出的最新葉子
): TranscriptMessage[] {
  const transcript: TranscriptMessage[] = []
  const seen = new Set<UUID>()
  let currentMsg: TranscriptMessage | undefined = leafMessage
  // 從葉子向根遍歷
  while (currentMsg) {
    // 環(huán)檢測:防御性編程
    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)
    // 尾插到數(shù)組(結(jié)果是倒序的)
    transcript.push(currentMsg)
    // 通過 parentUuid 在 HashMap 中 O(1) 查找上一條消息
    currentMsg = currentMsg.parentUuid
      ? messages.get(currentMsg.parentUuid)
      : undefined
  }
  // 反轉(zhuǎn)數(shù)組:從倒序變?yōu)檎颍ǜ?→ 葉)
  transcript.reverse()
  // 修復(fù)并行工具調(diào)用的孤兒結(jié)果
  return recoverOrphanedParallelToolResults(messages, transcript, seen)
}

回溯過程示意

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,無子節(jié)點)

回溯:
  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]  ← 正確的對話順序(從第一條到最新)

三個關(guān)鍵機制

機制說明
O(1) HashMap 查找messages.get(parentUuid) 不依賴 JSONL 中行的物理順序
環(huán)檢測如果鏈表出現(xiàn)環(huán)(數(shù)據(jù)損壞),記錄錯誤并退出,返回部分鏈
不完整鏈自動終止如果 parentUuid 指向的 UUID 不在 Map 中,get() 返回 undefined,遍歷自動終止

步驟 4:recoverOrphanedParallelToolResults()— 恢復(fù)并行工具的孤兒結(jié)果

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

這是恢復(fù)過程中最精妙的部分。要理解它,必須先理解兩個問題:

  1. 當(dāng) LLM 發(fā)出并行工具調(diào)用時,JSONL 里到底寫了什么?
  2. buildConversationChain 回溯后,為什么有些消息丟了?

4.1 前置知識:并行工具調(diào)用的 JSONL 寫入

假設(shè) LLM 在一次回復(fù)中同時發(fā)出 3 個工具調(diào)用:Bash、Read、Grep。

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", ... },
  ]

這三個工具并行執(zhí)行,各自獨立完成。每個工具完成后,結(jié)果被包裝為一個 user 消息寫入 JSONL(通過 recordTranscript → insertMessageChain)。

關(guān)鍵在于 insertMessageChain 寫入時 parentUuid 是如何分配的??创a:

// src/utils/sessionStorage.ts:1001-1068(簡化)
let parentUuid: UUID | null = startingParentUuid ?? null  // 初始值
for (const message of messages) {
  // ★ 關(guān)鍵:對 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 中實際寫入的內(nèi)容是:

消息      type          JSONL 中的 parentUuid    原因
──────────────────────────────────────────────────────────────────
A        user          null                     用戶的第一條輸入
B        assistant     A                        正常鏈(B 的 parent 是 A)
C        user          B           ←★            Bash 結(jié)果,sourceToolAssistantUUID = B
D        user          B           ←★            Read 結(jié)果,sourceToolAssistantUUID = B
E        user          B           ←★            Grep 結(jié)果,sourceToolAssistantUUID = B
F        assistant     E           ←★            順序鏈:F 的 effectiveParentUuid = parentUuid 變量 = E

關(guān)鍵洞察:

  • C、D、E 的 parentUuid 都是 B —— 它們都是 B 的"子節(jié)點"
  • F 的 parentUuid 是 E —— 因為寫入 E 之后 parentUuid 變量被更新為 E

4.2 問題:回溯時發(fā)生了什么

當(dāng) buildConversationChain 從葉子 F 開始回溯:

從葉子 F 出發(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 丟了!

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 只能表達一對多的"一"那側(cè)。消息 B 有三個子節(jié)點(C、D、E),但每個子節(jié)點只能有一個 parentUuid 值。回溯時只能沿著一條路徑走(B→E→F),其他路徑(B→C、B→D)被遺漏了。

4.3 恢復(fù)算法詳解

recoverOrphanedParallelToolResults 的目的就是找回這些孤兒。

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

const chainAssistants = chain.filter(m => m.type === 'assistant')
// 結(jié)果: [B, F]  (鏈上的兩條 assistant)

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

// 遍歷 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 子節(jié)點,所以不在 Map 中)

第三步:對每個鏈上的 assistant,找出孤兒

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 → 無操作

第四步:按時間戳排序孤兒

orphanedTRs.sort((a, b) => a.timestamp.localeCompare(b.timestamp))
// C 先完成 (ts: 04:20:01), D 后完成 (ts: 04:20:02)
// sorted = [C, D]

第五步:將孤兒插入到正確位置

// "錨點" = 觸發(fā)這些 tool_use 的 assistant 消息本身
// 孤兒應(yīng)該出現(xiàn)在 assistant 和下一個 assistant 之間
const anchor = asst    // 即 B
inserts.set(anchor.uuid, [C, D])
// 含義:在 B 后 面插入 [C, D]

最后:重建數(shù)組

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 最終效果對比

回溯得到的鏈(修復(fù)前):
  [A, B, E, F]
   ↑  ↑     ↑
   │  │     └─ 第二輪 assistant
   │  └─────── 只有 Grep 的 tool_result(最后完成的那個)
   └────────── 用戶輸入

修復(fù)后的鏈:
  [A, B, C, D, E, F]
   ↑  ↑  ↑  ↑  ↑  ↑
   │  │  │  │  │  └─ 第二輪 assistant
   │  │  │  │  └──── Grep 結(jié)果(最后完成)
   │  │  │  └─────── Read 結(jié)果(恢復(fù)的孤兒)
   │  │  └────────── Bash 結(jié)果(恢復(fù)的孤兒)
   │  └───────────── 包含 Bash+Read+Grep 的 assistant
   └──────────────── 用戶輸入

4.5 算法的通用性

這個算法不僅處理單個 assistant 發(fā)出多個 tool_use 的情況,還處理:

場景示例恢復(fù)方式
單個 assistant 發(fā)出 N 個并行工具B 發(fā)出 Bash+Read+Grep恢復(fù) N-1 個孤兒 tool_result
多個 assistant 各自發(fā)出工具B1 發(fā)出 Bash, B2 發(fā)出 Read每輪獨立恢復(fù)
同一 API 請求的多個分片(streaming)B 被分成 B?、B? 兩個消息siblingsByMsgId 按 message.id 分組處理
嵌套工具調(diào)用Tool A 觸發(fā) Agent B,Agent B 觸發(fā) Tool C每層 independent 恢復(fù)

4.6 一句話總結(jié)

并行工具調(diào)用的結(jié)果在 JSONL 中都指向同一個 assistant 消息作為 parent。但 parentUuid 鏈是線性的——從葉子回溯只能走到最后完成的那條 tool_result。recoverOrphanedParallelToolResults 通過反向索引(parentUuid → tool_result 列表)找到所有孤兒,按時間戳排序后插回正確位置。

步驟 5: Compact 恢復(fù)的特殊處理

preservedSegment 機制

當(dāng)發(fā)生 compact 時,大部分舊消息被摘要替代,但最近幾條核心對話可以標記為"保留段"保留在 JSONL 中。

applyPreservedSegmentRelinks

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

恢復(fù)前的物理 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(不在保留段內(nèi))        ← 剪枝
  4. msg_3, msg_4 的 assistant 的 token usage 清零  ← 防止虛假 autocompact
恢復(fù)后的邏輯鏈:
  ... → [boundary → summary] → [msg_3 → msg_4] → [new_msg → ...]

applySnipRemovals

Snip 是一種精確的上下文裁剪操作(按消息 UUID 范圍刪除)?;謴?fù)時:

  • 識別被 snip 標記的消息
  • 從 Map 中移除它們
  • 重新鏈接受影響的 parentUuid

完整恢復(fù)流水線

                    磁盤上的 JSONL 文件
                   (按時間追加,無序行)
                            │
        ┌───────────────────┼───────────────────┐
        ▼                   ▼                   ▼
   逐行 JSON.parse     分類到各容器         計算 leafUuids
   Entry 對象          Map<UUID, Msg>     (無子節(jié)點的消息 UUID)
        │                   │                   │
        └───────────────────┼───────────────────┘
                            │
                            ▼
                  findLatestMessage()
                 選 timestamp 最大的葉子
                            │
                            ▼
                buildConversationChain()
               葉子 → parentUuid 回溯 → 根
                環(huán)檢測 + reverse 反轉(zhuǎn)
                            │
                            ▼
          recoverOrphanedParallelToolResults()
               恢復(fù)并行工具的孤兒結(jié)果
                 插入到正確鏈位置
                            │
                            ▼
          applyPreservedSegmentRelinks()
               重新鏈接 compact 保留段
                 清零舊 token usage
                            │
                            ▼
          applySnipRemovals()
               刪除 snip 標記的消息
                            │
                            ▼
                TranscriptMessage[]
            [msg_1, msg_2, ..., msg_N]
              從根到葉,完整有序的對話

核心設(shè)計思想

單向鏈表 + HashMap 索引 = 簡單而強大的持久化方案

  1. 寫入簡單: 只需追加行,無需維護任何 B-tree 或索引結(jié)構(gòu)
  2. 恢復(fù)高效: parentUuid 指針 + HashMap 實現(xiàn) O(1) 跳轉(zhuǎn),從葉子走到根即完成恢復(fù)
  3. 容錯性好: 環(huán)檢測、不完整鏈自動終止、legacy progress 橋接
  4. 并行安全: recoverOrphanedParallelToolResults 修復(fù)并行工具調(diào)用導(dǎo)致的鏈斷裂
  5. 內(nèi)存優(yōu)化: 大文件場景下的逐塊讀取、死分支跳過、fd 級過濾

關(guān)鍵源文件索引

文件函數(shù)作用
src/utils/sessionStorage.ts:3472loadTranscriptFile()解析 JSONL,構(gòu)建 Map 和元數(shù)據(jù)
src/utils/sessionStorage.ts:3818loadSessionFile()定位 session JSONL 文件并調(diào)用 loadTranscriptFile
src/utils/sessionStorage.ts:3842getSessionMessages()獲取 session 所有消息 UUID(用于去重)
src/utils/sessionStorage.ts:2046findLatestMessage()從葉子中選 timestamp 最新的
src/utils/sessionStorage.ts:2069buildConversationChain()核心:從葉子沿 parentUuid 回溯到根
src/utils/sessionStorage.ts:2118recoverOrphanedParallelToolResults()修復(fù)并行工具調(diào)用的孤兒結(jié)果
src/utils/sessionStorage.ts:1839applyPreservedSegmentRelinks()重新鏈接 compact 保留段
src/utils/sessionStorage.ts:2294loadTranscriptFromFile()完整的文件→LogOption 轉(zhuǎn)換
src/utils/conversationRecovery.ts:416loadMessagesFromJsonlPath()從 JSONL 路徑加載消息的入口之一
src/types/logs.ts:221TranscriptMessage對話消息類型定義(含 parentUuid 字段)
src/types/logs.ts:297Entry所有 JSONL 條目類型的聯(lián)合類型

到此這篇關(guān)于Claude Code恢復(fù)session對話完整歷史的實現(xiàn)步驟的文章就介紹到這了,更多相關(guān)Claude Code恢復(fù)session歷史內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持腳本之家!

相關(guān)文章

  • Claude Code的會話恢復(fù)與多窗口使用指南(2026年)

    在 Claude Code 里,session(會話) 可以理解成一段完整的對話歷史,本文將通過一篇文章和大家講清楚什么時候該恢復(fù)會話,怎么恢復(fù),多個窗口怎么區(qū)分,怎么避免聊亂,感興
    2026-04-21

最新評論

伊吾县| 泰安市| 贞丰县| 扎兰屯市| 嘉兴市| 盘锦市| 五原县| 蛟河市| 黄冈市| 温州市| 和平县| 商水县| 松江区| 阆中市| 竹溪县| 吉首市| 灵宝市| 忻州市| 柏乡县| 兴仁县| 滁州市| 张掖市| 南昌县| 保康县| 泰宁县| 同德县| 加查县| 新巴尔虎右旗| 长沙县| 璧山县| 锡林浩特市| 济南市| 乌鲁木齐县| 吐鲁番市| 徐水县| 龙岩市| 施秉县| 马公市| 江山市| 和平县| 金山区|