* Splice the preserved segment back into the chain after compaction. * * Preserved messages exist in the JSONL with their ORIGINAL pre-compact * parentUuids (recordTranscript dedup-skipped them — can't rewrite). * The internal chain (keep[i+1]→keep[i]) is intact; only endpoints need * patching:
( messages: Map<UUID, TranscriptMessage>, )
| 1995 | * Mutates the Map in place. |
| 1996 | */ |
| 1997 | function applyPreservedSegmentRelinks( |
| 1998 | messages: Map<UUID, TranscriptMessage>, |
| 1999 | ): void { |
| 2000 | type Seg = NonNullable< |
| 2001 | SystemCompactBoundaryMessage['compactMetadata']['preservedSegment'] |
| 2002 | > |
| 2003 | |
| 2004 | // Find the absolute-last boundary and the last seg-boundary (can differ: |
| 2005 | // manual /compact after reactive compact → seg is stale). |
| 2006 | let lastSeg: Seg | undefined |
| 2007 | let lastSegBoundaryIdx = -1 |
| 2008 | let absoluteLastBoundaryIdx = -1 |
| 2009 | const entryIndex = new Map<UUID, number>() |
| 2010 | let i = 0 |
| 2011 | for (const entry of messages.values()) { |
| 2012 | entryIndex.set(entry.uuid, i) |
| 2013 | if (isCompactBoundaryMessage(entry)) { |
| 2014 | absoluteLastBoundaryIdx = i |
| 2015 | const seg = entry.compactMetadata?.preservedSegment |
| 2016 | if (seg) { |
| 2017 | lastSeg = seg |
| 2018 | lastSegBoundaryIdx = i |
| 2019 | } |
| 2020 | } |
| 2021 | i++ |
| 2022 | } |
| 2023 | // No seg anywhere → no-op. findUnresolvedToolUse etc. read the full map. |
| 2024 | if (!lastSeg) return |
| 2025 | |
| 2026 | // Seg stale (no-seg boundary came after): skip relink, still prune at |
| 2027 | // absolute — otherwise the stale preserved chain becomes a phantom leaf. |
| 2028 | const segIsLive = lastSegBoundaryIdx === absoluteLastBoundaryIdx |
| 2029 | |
| 2030 | // Validate tail→head BEFORE mutating so malformed metadata is a true |
| 2031 | // no-op (walk stops at headUuid, doesn't need the relink to run first). |
| 2032 | const preservedUuids = new Set<UUID>() |
| 2033 | if (segIsLive) { |
| 2034 | const walkSeen = new Set<UUID>() |
| 2035 | let cur = messages.get(lastSeg.tailUuid) |
| 2036 | let reachedHead = false |
| 2037 | while (cur && !walkSeen.has(cur.uuid)) { |
| 2038 | walkSeen.add(cur.uuid) |
| 2039 | preservedUuids.add(cur.uuid) |
| 2040 | if (cur.uuid === lastSeg.headUuid) { |
| 2041 | reachedHead = true |
| 2042 | break |
| 2043 | } |
| 2044 | cur = cur.parentUuid ? messages.get(cur.parentUuid) : undefined |
| 2045 | } |
| 2046 | if (!reachedHead) { |
| 2047 | // tail→head walk broke — a UUID in the preserved segment isn't in the |
| 2048 | // transcript. Returning here skips the prune below, so resume loads |
| 2049 | // the full pre-compact history. Known cause: mid-turn-yielded |
| 2050 | // attachment pushed to mutableMessages but never recordTranscript'd |
| 2051 | // (SDK subprocess restarted before next turn's qe:420 flush). |
| 2052 | logEvent('ncode_relink_walk_broken', { |
| 2053 | tailInTranscript: messages.has(lastSeg.tailUuid), |
| 2054 | headInTranscript: messages.has(lastSeg.headUuid), |
no test coverage detected