From ce068a8a55321c0ceb8e36883f535c5c27b4190d Mon Sep 17 00:00:00 2001 From: Jiayuan Date: Thu, 15 Jan 2026 23:41:35 +0800 Subject: [PATCH] fix: support new MessageContent format in history replay Added extractUserMessageText() helper to handle both new (MessageContent[]) and old ({ text: string }) user message formats. This fixes history replay for resumed sessions after commit 913cccc changed the message storage format without updating the extraction logic. Also updated integration test to use the new MessageContent format. Co-Authored-By: Claude Haiku 4.5 --- src/main/conductor/historyReplay.ts | 31 +++++++++++++++++-- tests/integration/conductor/Conductor.test.ts | 2 +- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/main/conductor/historyReplay.ts b/src/main/conductor/historyReplay.ts index 893378cd66..b47c776748 100644 --- a/src/main/conductor/historyReplay.ts +++ b/src/main/conductor/historyReplay.ts @@ -3,10 +3,35 @@ * Formats conversation history for prepending to prompts when agent restarts */ import type { StoredSessionUpdate } from '../../shared/types' +import type { MessageContent } from '../../shared/types/message' // Token estimation: ~4 bytes per token (same heuristic as Codex) const APPROX_BYTES_PER_TOKEN = 4 +/** + * Extract text from user message content + * Supports both new format (MessageContent[]) and old format ({ text: string }) + */ +function extractUserMessageText(content: unknown): string { + if (!content) return '' + + // New format: MessageContent[] (array of content items) + if (Array.isArray(content)) { + const textItem = (content as MessageContent).find((item) => item.type === 'text') + return textItem?.type === 'text' ? textItem.text : '' + } + + // Old format: { type: 'text', text: string } or { text: string } + if (typeof content === 'object') { + const obj = content as { text?: string } + if (typeof obj.text === 'string') { + return obj.text + } + } + + return '' +} + // Default max tokens for history (leaves room for user prompt and response) const DEFAULT_MAX_HISTORY_TOKENS = 20000 @@ -58,7 +83,8 @@ function extractMessages(updates: StoredSessionUpdate[]): ExtractedMessage[] { } // Add user message (custom internal type) - const content = (inner as { content?: { text?: string } }).content?.text || '' + const rawContent = (inner as { content?: unknown }).content + const content = extractUserMessageText(rawContent) if (content) { messages.push({ role: 'user', content }) } @@ -207,7 +233,8 @@ export function hasReplayableHistory(updates: StoredSessionUpdate[]): boolean { const updateType = inner.sessionUpdate as string if (updateType === 'user_message') { - const content = (inner as { content?: { text?: string } }).content?.text + const rawContent = (inner as { content?: unknown }).content + const content = extractUserMessageText(rawContent) if (content) { hasUser = true } diff --git a/tests/integration/conductor/Conductor.test.ts b/tests/integration/conductor/Conductor.test.ts index 2635b58170..663e842249 100644 --- a/tests/integration/conductor/Conductor.test.ts +++ b/tests/integration/conductor/Conductor.test.ts @@ -203,7 +203,7 @@ describe('Conductor', () => { const session = await conductor.createSession('/test/project', mockAgentConfig) // sendPrompt should trigger status changes - await conductor.sendPrompt(session.id, 'Hello') + await conductor.sendPrompt(session.id, [{ type: 'text', text: 'Hello' }]) // Should be called at least twice (start processing, end processing) expect(onStatusChange).toHaveBeenCalled()