From c9ca1697e55e4daeb82ce0bb0d2b66991e376344 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Mon, 23 Mar 2026 12:29:23 +0700 Subject: [PATCH] fix(server): free finalized message text in TimelineAssembler TimelineAssembler.messages map retained full assistantText and reasoningText for every message for the lifetime of the session. Replace with a lightweight finalizedMessageIds set that prevents duplicate emission during history replay without holding the text. --- packages/server/src/server/agent/providers/claude-agent.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/server/src/server/agent/providers/claude-agent.ts b/packages/server/src/server/agent/providers/claude-agent.ts index 8d7ebf500..c27355636 100644 --- a/packages/server/src/server/agent/providers/claude-agent.ts +++ b/packages/server/src/server/agent/providers/claude-agent.ts @@ -770,6 +770,7 @@ type TimelineMessageState = { class TimelineAssembler { private readonly messages = new Map(); + private readonly finalizedMessageIds = new Set(); private readonly activeMessageByRun = new Map(); private syntheticMessageCounter = 0; @@ -799,6 +800,9 @@ class TimelineAssembler { if (!messageId) { return []; } + if (this.finalizedMessageIds.has(messageId)) { + return []; + } const state = this.ensureMessageState(messageId, runId); const fragments = this.extractFragments(message.message?.content); return this.applyAbsoluteFragments(state, fragments); @@ -922,6 +926,8 @@ class TimelineAssembler { if (runId && this.activeMessageByRun.get(runId) === messageId) { this.activeMessageByRun.delete(runId); } + this.finalizedMessageIds.add(messageId); + this.messages.delete(messageId); return items; }