mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
fix(vscode): preserve older messages when paginating virtualized chat (#9194)
The fill() helper that repairs pages starting with an assistant turn kept only the user-turn suffix of each older page, so earlier messages fetched mid-stitch were dropped while the pagination cursor still advanced past them. That made the top of the virtualized history land after the real first message. Keep the full older page when stitching partial turns so fill() terminates naturally once a user message reaches the top, and add a regression test covering multi-page fills.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Restore earlier chat history when scrolling through virtualized message lists.
|
||||
@@ -46,17 +46,11 @@ export async function fetchMessagePage(
|
||||
return { items, cursor }
|
||||
}
|
||||
|
||||
const suffix = (items: Awaited<ReturnType<typeof read>>["items"]) => {
|
||||
const index = [...items].reverse().findIndex((item) => item.info.role === "user")
|
||||
if (index === -1) return items
|
||||
return items.slice(items.length - index - 1)
|
||||
}
|
||||
|
||||
const fill = async (page: Awaited<ReturnType<typeof read>>): Promise<Awaited<ReturnType<typeof read>>> => {
|
||||
if (page.items[0]?.info.role !== "assistant") return page
|
||||
if (!page.cursor || input.signal?.aborted) return page
|
||||
const next = await read(page.cursor)
|
||||
const items = [...suffix(next.items), ...page.items]
|
||||
const items = [...next.items, ...page.items]
|
||||
return fill({ items, cursor: next.cursor })
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ describe("fetchMessagePage / cursor fallback", () => {
|
||||
})
|
||||
|
||||
it("synthesized cursor round-trips through the server's before parameter", async () => {
|
||||
// First page: server strips header, items fill limit → cursor synthesized.
|
||||
// First page: server strips header, items fill limit -> cursor synthesized.
|
||||
// Next page request uses that cursor and returns more items.
|
||||
const { client, calls } = mockClient([
|
||||
{
|
||||
@@ -115,4 +115,53 @@ describe("fetchMessagePage / cursor fallback", () => {
|
||||
})
|
||||
expect(calls[1]?.before).toBe(first.cursor)
|
||||
})
|
||||
|
||||
it("keeps all fetched older messages when filling a partial assistant turn", async () => {
|
||||
const { client, calls } = mockClient([
|
||||
{
|
||||
items: [message("m4", "assistant", 40), message("m5", "user", 50)],
|
||||
cursor: "c1",
|
||||
},
|
||||
{
|
||||
items: [message("m1", "user", 10), message("m2", "assistant", 20), message("m3", "user", 30)],
|
||||
cursor: "c2",
|
||||
},
|
||||
])
|
||||
|
||||
const page = await fetchMessagePage(client as never, {
|
||||
sessionID: "s1",
|
||||
workspaceDir: "/repo",
|
||||
limit: 3,
|
||||
})
|
||||
|
||||
expect(calls.map((call) => call.before)).toEqual([undefined, "c1"])
|
||||
expect(page.items.map((item) => item.info.id)).toEqual(["m1", "m2", "m3", "m4", "m5"])
|
||||
expect(page.cursor).toBe("c2")
|
||||
})
|
||||
|
||||
it("continues fetching until a partial assistant turn reaches the first user message", async () => {
|
||||
const { client, calls } = mockClient([
|
||||
{
|
||||
items: [message("m4", "assistant", 40), message("m5", "user", 50)],
|
||||
cursor: "c1",
|
||||
},
|
||||
{
|
||||
items: [message("m2", "assistant", 20), message("m3", "user", 30)],
|
||||
cursor: "c2",
|
||||
},
|
||||
{
|
||||
items: [message("m1", "user", 10)],
|
||||
},
|
||||
])
|
||||
|
||||
const page = await fetchMessagePage(client as never, {
|
||||
sessionID: "s1",
|
||||
workspaceDir: "/repo",
|
||||
limit: 2,
|
||||
})
|
||||
|
||||
expect(calls.map((call) => call.before)).toEqual([undefined, "c1", "c2"])
|
||||
expect(page.items.map((item) => item.info.id)).toEqual(["m1", "m2", "m3", "m4", "m5"])
|
||||
expect(page.cursor).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user