fix(session): prevent double auto-compaction from filterCompacted reorder (#27545)

This commit is contained in:
Kit Langton
2026-05-14 13:56:12 -04:00
committed by GitHub
parent 855bda8384
commit 94564f3588
3 changed files with 135 additions and 13 deletions

View File

@@ -1067,6 +1067,33 @@ export const filterCompactedEffect = Effect.fnUntraced(function* (sessionID: Ses
return filterCompacted(stream(sessionID))
})
// filterCompacted reorders messages for model consumption
// ([compaction-user, summary, ...retained tail..., continue-user]), so array
// position is not chronological. Derive each binding by max id (MessageID
// is monotonic via MessageID.ascending) so a pre-compaction overflowing tail
// assistant doesn't get mistaken for the most recent turn. tasks are
// compaction/subtask parts attached to user messages newer than the latest
// finished assistant — i.e. unprocessed work.
export function latest(msgs: WithParts[]) {
let user: User | undefined
let assistant: Assistant | undefined
let finished: Assistant | undefined
for (const msg of msgs) {
const info = msg.info
if (info.role === "user" && (!user || info.id > user.id)) user = info
if (info.role === "assistant" && (!assistant || info.id > assistant.id)) assistant = info
if (info.role === "assistant" && info.finish && (!finished || info.id > finished.id)) finished = info
}
const tasks = msgs.flatMap((m) =>
finished && m.info.id <= finished.id
? []
: m.parts.filter(
(p): p is CompactionPart | SubtaskPart => p.type === "compaction" || p.type === "subtask",
),
)
return { user, assistant, finished, tasks }
}
export function fromError(
e: unknown,
ctx: { providerID: ProviderID; aborted?: boolean },

View File

@@ -1654,19 +1654,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
let msgs = yield* MessageV2.filterCompactedEffect(sessionID)
let lastUser: MessageV2.User | undefined
let lastAssistant: MessageV2.Assistant | undefined
let lastFinished: MessageV2.Assistant | undefined
let tasks: (MessageV2.CompactionPart | MessageV2.SubtaskPart)[] = []
for (let i = msgs.length - 1; i >= 0; i--) {
const msg = msgs[i]
if (!lastUser && msg.info.role === "user") lastUser = msg.info
if (!lastAssistant && msg.info.role === "assistant") lastAssistant = msg.info
if (!lastFinished && msg.info.role === "assistant" && msg.info.finish) lastFinished = msg.info
if (lastUser && lastFinished) break
const task = msg.parts.filter((part) => part.type === "compaction" || part.type === "subtask")
if (task && !lastFinished) tasks.push(...task)
}
const { user: lastUser, assistant: lastAssistant, finished: lastFinished, tasks } = MessageV2.latest(msgs)
if (!lastUser) throw new Error("No user message found in stream. This should never happen.")