Skip to content

Commit 9b8a7da

Browse files
authored
fix: history jsonl file corruption cases (#4364)
1 parent 61fd211 commit 9b8a7da

1 file changed

Lines changed: 35 additions & 6 deletions

File tree

  • packages/opencode/src/cli/cmd/tui/component/prompt

packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { onMount } from "solid-js"
44
import { createStore, produce } from "solid-js/store"
55
import { clone } from "remeda"
66
import { createSimpleContext } from "../../context/helper"
7-
import { appendFile } from "fs/promises"
7+
import { appendFile, writeFile } from "fs/promises"
88
import type { AgentPart, FilePart, TextPart } from "@opencode-ai/sdk"
99

1010
export type PromptInfo = {
@@ -24,6 +24,8 @@ export type PromptInfo = {
2424
)[]
2525
}
2626

27+
const MAX_HISTORY_ENTRIES = 50
28+
2729
export const { use: usePromptHistory, provider: PromptHistoryProvider } = createSimpleContext({
2830
name: "PromptHistory",
2931
init: () => {
@@ -33,8 +35,23 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create
3335
const lines = text
3436
.split("\n")
3537
.filter(Boolean)
36-
.map((line) => JSON.parse(line))
37-
setStore("history", lines as PromptInfo[])
38+
.map((line) => {
39+
try {
40+
return JSON.parse(line)
41+
} catch {
42+
return null
43+
}
44+
})
45+
.filter((line): line is PromptInfo => line !== null)
46+
.slice(-MAX_HISTORY_ENTRIES)
47+
48+
setStore("history", lines)
49+
50+
// Rewrite file with only valid entries to self-heal corruption
51+
if (lines.length > 0) {
52+
const content = lines.map((line) => JSON.stringify(line)).join("\n") + "\n"
53+
writeFile(historyFile.name!, content).catch(() => {})
54+
}
3855
})
3956

4057
const [store, setStore] = createStore({
@@ -64,14 +81,26 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create
6481
return store.history.at(store.index)
6582
},
6683
append(item: PromptInfo) {
67-
item = clone(item)
68-
appendFile(historyFile.name!, JSON.stringify(item) + "\n")
84+
const entry = clone(item)
85+
let trimmed = false
6986
setStore(
7087
produce((draft) => {
71-
draft.history.push(item)
88+
draft.history.push(entry)
89+
if (draft.history.length > MAX_HISTORY_ENTRIES) {
90+
draft.history = draft.history.slice(-MAX_HISTORY_ENTRIES)
91+
trimmed = true
92+
}
7293
draft.index = 0
7394
}),
7495
)
96+
97+
if (trimmed) {
98+
const content = store.history.map((line) => JSON.stringify(line)).join("\n") + "\n"
99+
writeFile(historyFile.name!, content).catch(() => {})
100+
return
101+
}
102+
103+
appendFile(historyFile.name!, JSON.stringify(entry) + "\n").catch(() => {})
75104
},
76105
}
77106
},

0 commit comments

Comments
 (0)