From 2dce0a99b238b9dddee17e49b3eab29f99ddffe0 Mon Sep 17 00:00:00 2001 From: Stephen King Date: Wed, 28 Jan 2026 12:29:12 -0700 Subject: [PATCH] fix: add parentId to appendAssistantTranscriptMessage to preserve history chain Messages appended via /status (and other assistant transcript injections) were written with no parentId, breaking the tree structure that history reconstruction walks. This caused context to be truncated at these injection points. The fix reads the current leaf message ID before appending and includes it as parentId in the new entry, preserving the chain. --- src/gateway/server-methods/chat.ts | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/gateway/server-methods/chat.ts b/src/gateway/server-methods/chat.ts index 9010a6f21..42ec8b1d8 100644 --- a/src/gateway/server-methods/chat.ts +++ b/src/gateway/server-methods/chat.ts @@ -82,6 +82,36 @@ function ensureTranscriptFile(params: { transcriptPath: string; sessionId: strin } } +/** + * Find the leaf (last message) ID from a transcript file. + * Returns null if file doesn't exist, is empty, or has no messages (header only). + */ +function findTranscriptLeafId(transcriptPath: string): string | null { + if (!fs.existsSync(transcriptPath)) return null; + + try { + const content = fs.readFileSync(transcriptPath, "utf-8"); + const lines = content.split(/\r?\n/).filter((line) => line.trim()); + if (lines.length === 0) return null; + + // Walk backwards to find the last message entry with an id + for (let i = lines.length - 1; i >= 0; i--) { + try { + const parsed = JSON.parse(lines[i]); + // Skip non-message entries (like session headers) + if (parsed?.type === "message" && typeof parsed?.id === "string") { + return parsed.id; + } + } catch { + // Skip malformed lines + } + } + return null; + } catch { + return null; + } +} + function appendAssistantTranscriptMessage(params: { message: string; label?: string; @@ -112,6 +142,9 @@ function appendAssistantTranscriptMessage(params: { } } + // Find the current leaf ID before we append + const parentId = findTranscriptLeafId(transcriptPath); + const now = Date.now(); const messageId = randomUUID().slice(0, 8); const labelPrefix = params.label ? `[${params.label}]\n\n` : ""; @@ -125,6 +158,7 @@ function appendAssistantTranscriptMessage(params: { const transcriptEntry = { type: "message", id: messageId, + parentId, timestamp: new Date(now).toISOString(), message: messageBody, };