diff --git a/src/hooks/session-recovery/index.ts b/src/hooks/session-recovery/index.ts index 70cd0b1..6027907 100644 --- a/src/hooks/session-recovery/index.ts +++ b/src/hooks/session-recovery/index.ts @@ -4,12 +4,14 @@ import { findEmptyMessages, findEmptyMessageByIndex, findMessageByIndexNeedingThinking, + findMessagesWithEmptyTextParts, findMessagesWithOrphanThinking, findMessagesWithThinkingBlocks, findMessagesWithThinkingOnly, injectTextPart, prependThinkingPart, readParts, + replaceEmptyTextParts, stripThinkingParts, } from "./storage" import type { MessageData } from "./types" @@ -222,28 +224,48 @@ async function recoverEmptyContentMessage( ): Promise { const targetIndex = extractMessageIndex(error) const failedID = failedAssistantMsg.info?.id + let anySuccess = false + + const messagesWithEmptyText = findMessagesWithEmptyTextParts(sessionID) + for (const messageID of messagesWithEmptyText) { + if (replaceEmptyTextParts(messageID, PLACEHOLDER_TEXT)) { + anySuccess = true + } + } const thinkingOnlyIDs = findMessagesWithThinkingOnly(sessionID) for (const messageID of thinkingOnlyIDs) { - injectTextPart(sessionID, messageID, PLACEHOLDER_TEXT) + if (injectTextPart(sessionID, messageID, PLACEHOLDER_TEXT)) { + anySuccess = true + } } if (targetIndex !== null) { const targetMessageID = findEmptyMessageByIndex(sessionID, targetIndex) if (targetMessageID) { - return injectTextPart(sessionID, targetMessageID, PLACEHOLDER_TEXT) + if (replaceEmptyTextParts(targetMessageID, PLACEHOLDER_TEXT)) { + return true + } + if (injectTextPart(sessionID, targetMessageID, PLACEHOLDER_TEXT)) { + return true + } } } if (failedID) { + if (replaceEmptyTextParts(failedID, PLACEHOLDER_TEXT)) { + return true + } if (injectTextPart(sessionID, failedID, PLACEHOLDER_TEXT)) { return true } } const emptyMessageIDs = findEmptyMessages(sessionID) - let anySuccess = thinkingOnlyIDs.length > 0 for (const messageID of emptyMessageIDs) { + if (replaceEmptyTextParts(messageID, PLACEHOLDER_TEXT)) { + anySuccess = true + } if (injectTextPart(sessionID, messageID, PLACEHOLDER_TEXT)) { anySuccess = true } diff --git a/src/hooks/session-recovery/storage.ts b/src/hooks/session-recovery/storage.ts index 3f50e94..c67f1fd 100644 --- a/src/hooks/session-recovery/storage.ts +++ b/src/hooks/session-recovery/storage.ts @@ -271,6 +271,55 @@ export function stripThinkingParts(messageID: string): boolean { return anyRemoved } +export function replaceEmptyTextParts(messageID: string, replacementText: string): boolean { + const partDir = join(PART_STORAGE, messageID) + if (!existsSync(partDir)) return false + + let anyReplaced = false + for (const file of readdirSync(partDir)) { + if (!file.endsWith(".json")) continue + try { + const filePath = join(partDir, file) + const content = readFileSync(filePath, "utf-8") + const part = JSON.parse(content) as StoredPart + + if (part.type === "text") { + const textPart = part as StoredTextPart + if (!textPart.text?.trim()) { + textPart.text = replacementText + textPart.synthetic = true + writeFileSync(filePath, JSON.stringify(textPart, null, 2)) + anyReplaced = true + } + } + } catch { + continue + } + } + + return anyReplaced +} + +export function findMessagesWithEmptyTextParts(sessionID: string): string[] { + const messages = readMessages(sessionID) + const result: string[] = [] + + for (const msg of messages) { + const parts = readParts(msg.id) + const hasEmptyTextPart = parts.some((p) => { + if (p.type !== "text") return false + const textPart = p as StoredTextPart + return !textPart.text?.trim() + }) + + if (hasEmptyTextPart) { + result.push(msg.id) + } + } + + return result +} + export function findMessageByIndexNeedingThinking(sessionID: string, targetIndex: number): string | null { const messages = readMessages(sessionID)