diff --git a/src/cli/helpers/accumulator.ts b/src/cli/helpers/accumulator.ts index 8554fb4..ecc0214 100644 --- a/src/cli/helpers/accumulator.ts +++ b/src/cli/helpers/accumulator.ts @@ -5,8 +5,12 @@ // - Exposes `onChunk` to feed SDK events and `toLines` to render. import type { LettaStreamingResponse } from "@letta-ai/letta-client/resources/agents/messages"; -import { INTERRUPTED_BY_USER } from "../../constants"; +import { + COMPACTION_SUMMARY_HEADER, + INTERRUPTED_BY_USER, +} from "../../constants"; import { runPostToolUseHooks, runPreToolUseHooks } from "../../hooks"; +import { extractCompactionSummary } from "./backfill"; import { findLastSafeSplitPoint } from "./markdownSplit"; import { isShellTool } from "./toolNameMapping"; @@ -505,6 +509,33 @@ export function onChunk(b: Buffers, chunk: LettaStreamingResponse) { break; } + case "user_message": { + // Use otid if available, fall back to id (server sends otid: null for summary messages) + const chunkWithId = chunk as LettaStreamingResponse & { id?: string }; + const id = chunk.otid || chunkWithId.id; + if (!id) break; + + // Handle otid transition (mark previous line as finished) + handleOtidTransition(b, id); + + // Extract text content from the user message + const rawText = extractTextPart(chunk.content); + if (!rawText) break; + + // Check if this is a compaction summary message + const compactionSummary = extractCompactionSummary(rawText); + if (compactionSummary) { + // Render as a user message with context header and summary + ensure(b, id, () => ({ + kind: "user", + id, + text: `${COMPACTION_SUMMARY_HEADER}\n\n${compactionSummary}`, + })); + } + // If not a summary, ignore it (user messages aren't rendered during streaming) + break; + } + case "tool_call_message": case "approval_request_message": { /* POST-FIX VERSION (what this should look like after backend fix): diff --git a/src/cli/helpers/backfill.ts b/src/cli/helpers/backfill.ts index 3f82a9d..2e55e64 100644 --- a/src/cli/helpers/backfill.ts +++ b/src/cli/helpers/backfill.ts @@ -5,7 +5,11 @@ import type { Message, TextContent, } from "@letta-ai/letta-client/resources/agents/messages"; -import { SYSTEM_REMINDER_CLOSE, SYSTEM_REMINDER_OPEN } from "../../constants"; +import { + COMPACTION_SUMMARY_HEADER, + SYSTEM_REMINDER_CLOSE, + SYSTEM_REMINDER_OPEN, +} from "../../constants"; import type { Buffers } from "./accumulator"; /** @@ -81,7 +85,7 @@ function truncateSystemReminder(text: string, maxLength: number): string { * Check if a user message is a compaction summary (system_alert with summary content). * Returns the summary text if found, null otherwise. */ -function extractCompactionSummary(text: string): string | null { +export function extractCompactionSummary(text: string): string | null { try { const parsed = JSON.parse(text); if ( @@ -182,17 +186,12 @@ export function backfillBuffers(buffers: Buffers, history: Message[]): void { // Check if this is a compaction summary message (system_alert with summary) const compactionSummary = extractCompactionSummary(rawText); if (compactionSummary) { - // Render as a synthetic tool call showing the compaction + // Render as a user message with context header and summary const exists = buffers.byId.has(lineId); buffers.byId.set(lineId, { - kind: "tool_call", + kind: "user", id: lineId, - toolCallId: `compaction-${lineId}`, - name: "Compact", - argsText: "messages[...]", - resultText: compactionSummary, - resultOk: true, - phase: "finished", + text: `${COMPACTION_SUMMARY_HEADER}\n\n${compactionSummary}`, }); if (!exists) buffers.order.push(lineId); break; diff --git a/src/constants.ts b/src/constants.ts index 01b129d..015510f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -24,6 +24,12 @@ export const SYSTEM_REMINDER_TAG = "system-reminder"; export const SYSTEM_REMINDER_OPEN = `<${SYSTEM_REMINDER_TAG}>`; export const SYSTEM_REMINDER_CLOSE = ``; +/** + * Header displayed before compaction summary when conversation context is truncated + */ +export const COMPACTION_SUMMARY_HEADER = + "This session is being continued from a previous conversation that ran out of context. The summary below covers the earlier portion of the conversation."; + /** * Status bar thresholds - only show indicators when values exceed these */