Browse Source

fix: history jsonl file corruption cases (#4364)

Aiden Cline 3 months ago
parent
commit
9b8a7da1e6
1 changed files with 35 additions and 6 deletions
  1. 35 6
      packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx

+ 35 - 6
packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx

@@ -4,7 +4,7 @@ import { onMount } from "solid-js"
 import { createStore, produce } from "solid-js/store"
 import { clone } from "remeda"
 import { createSimpleContext } from "../../context/helper"
-import { appendFile } from "fs/promises"
+import { appendFile, writeFile } from "fs/promises"
 import type { AgentPart, FilePart, TextPart } from "@opencode-ai/sdk"
 
 export type PromptInfo = {
@@ -24,6 +24,8 @@ export type PromptInfo = {
   )[]
 }
 
+const MAX_HISTORY_ENTRIES = 50
+
 export const { use: usePromptHistory, provider: PromptHistoryProvider } = createSimpleContext({
   name: "PromptHistory",
   init: () => {
@@ -33,8 +35,23 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create
       const lines = text
         .split("\n")
         .filter(Boolean)
-        .map((line) => JSON.parse(line))
-      setStore("history", lines as PromptInfo[])
+        .map((line) => {
+          try {
+            return JSON.parse(line)
+          } catch {
+            return null
+          }
+        })
+        .filter((line): line is PromptInfo => line !== null)
+        .slice(-MAX_HISTORY_ENTRIES)
+
+      setStore("history", lines)
+
+      // Rewrite file with only valid entries to self-heal corruption
+      if (lines.length > 0) {
+        const content = lines.map((line) => JSON.stringify(line)).join("\n") + "\n"
+        writeFile(historyFile.name!, content).catch(() => {})
+      }
     })
 
     const [store, setStore] = createStore({
@@ -64,14 +81,26 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create
         return store.history.at(store.index)
       },
       append(item: PromptInfo) {
-        item = clone(item)
-        appendFile(historyFile.name!, JSON.stringify(item) + "\n")
+        const entry = clone(item)
+        let trimmed = false
         setStore(
           produce((draft) => {
-            draft.history.push(item)
+            draft.history.push(entry)
+            if (draft.history.length > MAX_HISTORY_ENTRIES) {
+              draft.history = draft.history.slice(-MAX_HISTORY_ENTRIES)
+              trimmed = true
+            }
             draft.index = 0
           }),
         )
+
+        if (trimmed) {
+          const content = store.history.map((line) => JSON.stringify(line)).join("\n") + "\n"
+          writeFile(historyFile.name!, content).catch(() => {})
+          return
+        }
+
+        appendFile(historyFile.name!, JSON.stringify(entry) + "\n").catch(() => {})
       },
     }
   },