* 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>, )
| 1837 | * Mutates the Map in place. |
| 1838 | */ |
| 1839 | function applyPreservedSegmentRelinks( |
| 1840 | messages: Map<UUID, TranscriptMessage>, |
| 1841 | ): void { |
| 1842 | type Seg = NonNullable< |
| 1843 | SystemCompactBoundaryMessage['compactMetadata']['preservedSegment'] |
| 1844 | > |
| 1845 | |
| 1846 | // Find the absolute-last boundary and the last seg-boundary (can differ: |
| 1847 | // manual /compact after reactive compact → seg is stale). |
| 1848 | let lastSeg: Seg | undefined |
| 1849 | let lastSegBoundaryIdx = -1 |
| 1850 | let absoluteLastBoundaryIdx = -1 |
| 1851 | const entryIndex = new Map<UUID, number>() |
| 1852 | let i = 0 |
| 1853 | for (const entry of messages.values()) { |
| 1854 | entryIndex.set(entry.uuid, i) |
| 1855 | if (isCompactBoundaryMessage(entry)) { |
| 1856 | absoluteLastBoundaryIdx = i |
| 1857 | const seg = entry.compactMetadata?.preservedSegment |
| 1858 | if (seg) { |
| 1859 | lastSeg = seg |
| 1860 | lastSegBoundaryIdx = i |
| 1861 | } |
| 1862 | } |
| 1863 | i++ |
| 1864 | } |
| 1865 | // No seg anywhere → no-op. findUnresolvedToolUse etc. read the full map. |
| 1866 | if (!lastSeg) return |
| 1867 | |
| 1868 | // Seg stale (no-seg boundary came after): skip relink, still prune at |
| 1869 | // absolute — otherwise the stale preserved chain becomes a phantom leaf. |
| 1870 | const segIsLive = lastSegBoundaryIdx === absoluteLastBoundaryIdx |
| 1871 | |
| 1872 | // Validate tail→head BEFORE mutating so malformed metadata is a true |
| 1873 | // no-op (walk stops at headUuid, doesn't need the relink to run first). |
| 1874 | const preservedUuids = new Set<UUID>() |
| 1875 | if (segIsLive) { |
| 1876 | const walkSeen = new Set<UUID>() |
| 1877 | let cur = messages.get(lastSeg.tailUuid) |
| 1878 | let reachedHead = false |
| 1879 | while (cur && !walkSeen.has(cur.uuid)) { |
| 1880 | walkSeen.add(cur.uuid) |
| 1881 | preservedUuids.add(cur.uuid) |
| 1882 | if (cur.uuid === lastSeg.headUuid) { |
| 1883 | reachedHead = true |
| 1884 | break |
| 1885 | } |
| 1886 | cur = cur.parentUuid ? messages.get(cur.parentUuid) : undefined |
| 1887 | } |
| 1888 | if (!reachedHead) { |
| 1889 | // tail→head walk broke — a UUID in the preserved segment isn't in the |
| 1890 | // transcript. Returning here skips the prune below, so resume loads |
| 1891 | // the full pre-compact history. Known cause: mid-turn-yielded |
| 1892 | // attachment pushed to mutableMessages but never recordTranscript'd |
| 1893 | // (SDK subprocess restarted before next turn's qe:420 flush). |
| 1894 | logEvent('tengu_relink_walk_broken', { |
| 1895 | tailInTranscript: messages.has(lastSeg.tailUuid), |
| 1896 | headInTranscript: messages.has(lastSeg.headUuid), |
no test coverage detected