fix(opencode): serialize orphaned compaction history (#40800)

Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
This commit is contained in:
opencode-agent[bot]
2026-08-06 21:11:14 -05:00
committed by GitHub
parent 69f2cbaa3a
commit b7f9363393
2 changed files with 119 additions and 9 deletions
@@ -1362,10 +1362,10 @@ describe("session.compaction.process", () => {
"summarizes only the head while keeping recent tail out of summary input",
() => {
const stub = llm()
let captured = ""
let messages: LLM.StreamInput["messages"] = []
stub.push(
reply("summary", (input) => {
captured = JSON.stringify(input.messages)
messages = input.messages
}),
)
return Effect.gen(function* () {
@@ -1386,7 +1386,10 @@ describe("session.compaction.process", () => {
auto: false,
})
expect(captured).toContain("older context")
const captured = JSON.stringify(messages)
expect(messages).toHaveLength(1)
expect(messages[0]?.role).toBe("user")
expect(captured).toContain("[User]: older context")
expect(captured).not.toContain("keep this turn")
expect(captured).not.toContain("and this one too")
expect(captured).not.toContain("What did we do so far?")
@@ -1437,6 +1440,74 @@ describe("session.compaction.process", () => {
{ git: true },
)
itCompaction.instance(
"serializes repeated compaction history as one user message",
() => {
const stub = llm()
let captured: LLM.StreamInput["messages"] = []
stub.push(
reply("summary two", (input) => {
captured = input.messages
}),
)
return Effect.gen(function* () {
const ssn = yield* SessionNs.Service
const test = yield* TestInstance
const session = yield* ssn.create({})
const turn = yield* createUserMessage(session.id, "original request")
const kept = yield* createAssistantMessage(session.id, turn.id, test.directory)
yield* ssn.updatePart({
id: PartID.ascending(),
messageID: kept.id,
sessionID: session.id,
type: "tool",
callID: "read-call",
tool: "read",
state: {
status: "completed",
input: { filePath: "src/index.ts" },
output: "file contents",
title: "src/index.ts",
metadata: {},
time: { start: Date.now(), end: Date.now() },
},
})
const previous = yield* ssn.updateMessage({
id: MessageID.ascending(),
role: "user",
model: ref,
sessionID: session.id,
agent: "build",
time: { created: Date.now() },
})
yield* ssn.updatePart({
id: PartID.ascending(),
messageID: previous.id,
sessionID: session.id,
type: "compaction",
auto: false,
tail_start_id: kept.id,
})
yield* createSummaryAssistantMessage(session.id, previous.id, test.directory, "summary one")
yield* createCompactionMarker(session.id)
const msgs = MessageV2.filterCompacted(yield* MessageV2.stream(session.id))
const parent = msgs.at(-1)?.info.id
expect(parent).toBeTruthy()
yield* SessionCompaction.use.process({ parentID: parent!, messages: msgs, sessionID: session.id, auto: false })
expect(captured).toHaveLength(1)
expect(captured[0]?.role).toBe("user")
expect(JSON.stringify(captured)).toContain('[Assistant tool call]: read({\\"filePath\\":\\"src/index.ts\\"})')
expect(JSON.stringify(captured)).toContain("[Tool result]: file contents")
expect(JSON.stringify(captured)).not.toContain('\\"role\\":\\"assistant\\"')
}).pipe(withCompaction({ llm: stub.llmLayer, config: cfg({ tail_turns: 0 }) }))
},
{ git: true },
)
itCompaction.instance("keeps recent pre-compaction turns across repeated compactions", () => {
const stub = llm()
stub.push(reply("summary one"))