/
Alex_Ural
/
opencode
Обзор
Документация
Войти
/
Alex_Ural
/
opencode
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
packages/app/src/context/server-session.test.ts
1 476 строк
59 KB
opencode-agent[bot]
fix(app): tolerate missing message parents (#37918)
20 июл 2026, 15:20
Не верифицирован
20 июл 2026, 15:20
cc3615b
Код
Авторство
О чём код?
import { describe, expect, test } from "bun:test" import type { retry } from "@opencode-ai/core/util/retry" import type { Message, OpencodeClient, Part, Session } from "@opencode-ai/sdk/v2/client" import { createServerSession } from "./server-session" const session = (id: string, parentID?: string): Session => ({ id, slug: id, projectID: "project", directory: "/repo", title: id, version: "1", parentID, time: { created: 1, updated: 1 }, }) type UserMessage = Extract<Message, { role: "user" }> type AssistantMessage = Extract<Message, { role: "assistant" }> type TextPart = Extract<Part, { type: "text" }> type MessageResponse = { data: { info: Message; parts: Part[] }[] response: { headers: Headers } } type SingleMessageResponse = { data: MessageResponse["data"][number] } const userMessage = (id: string, input: Partial<UserMessage> = {}): UserMessage => ({ id, sessionID: "child", role: "user", time: { created: 1 }, agent: "build", model: { providerID: "provider", modelID: "model" }, ...input, }) const assistantMessage = (id: string, parentID: string, input: Partial<AssistantMessage> = {}): AssistantMessage => ({ id, sessionID: "child", role: "assistant", time: { created: Number(id.at(-1)), completed: Number(id.at(-1)) }, parentID, modelID: "model", providerID: "provider", mode: "build", agent: "build", path: { cwd: "/repo", root: "/repo" }, cost: 0, tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, ...input, }) const textPart = (messageID: string, input: Partial<TextPart> = {}): TextPart => ({ id: "part", sessionID: "child", messageID, type: "text", text: "text", ...input, }) const response = (data: MessageResponse["data"] = [], cursor?: string): MessageResponse => ({ data, response: { headers: new Headers(cursor ? { "x-next-cursor": cursor } : undefined) }, }) const singleResponse = (info: Message, parts: Part[] = []): SingleMessageResponse => ({ data: { info, parts } }) const deferredResponse = () => Promise.withResolvers<MessageResponse>() function messageClient(...responses: Array<MessageResponse | Promise<MessageResponse>>) { let index = 0 const requests: unknown[] = [] const waiting = new Map<number, () => void>() const client = { session: { get: async () => ({ data: session("child", "root") }), messages: (input: unknown) => { requests.push(input) waiting.get(requests.length)?.() waiting.delete(requests.length) return responses[index++] }, }, } as unknown as OpencodeClient return Object.assign(client, { requests, requested(count: number) { if (requests.length >= count) return Promise.resolve() return new Promise<void>((resolve) => waiting.set(count, resolve)) }, }) } function rootMessageClient( pages: Array<MessageResponse | Promise<MessageResponse>>, roots: Array<SingleMessageResponse | Promise<SingleMessageResponse>>, ) { let pageIndex = 0 let rootIndex = 0 const requests: unknown[] = [] const rootRequests: unknown[] = [] const rootWaiting = new Map<number, () => void>() const client = { session: { get: async () => ({ data: session("child", "root") }), messages: (input: unknown) => { requests.push(input) return pages[pageIndex++] }, message: (input: unknown) => { rootRequests.push(input) rootWaiting.get(rootRequests.length)?.() rootWaiting.delete(rootRequests.length) return roots[rootIndex++] }, }, } as unknown as OpencodeClient return Object.assign(client, { requests, rootRequests, rootRequested(count: number) { if (rootRequests.length >= count) return Promise.resolve() return new Promise<void>((resolve) => rootWaiting.set(count, resolve)) }, }) } const retryImmediately: typeof retry = async (task, options = {}) => { const attempts = options.attempts ?? 3 for (let attempt = 0; ; attempt++) { try { return await task() } catch (error) { if (attempt === attempts - 1) throw error } } } function setup(sessions: Record<string, Session>) { const get: unknown[] = [] const messages: unknown[] = [] const client = { session: { get: async (input: unknown) => { get.push(input) const id = (input as { sessionID: string }).sessionID return { data: sessions[id] } }, messages: async (input: unknown) => { messages.push(input) return response() }, diff: async () => ({ data: [] }), todo: async () => ({ data: [] }), }, } as unknown as OpencodeClient return { get, messages, store: createServerSession(client) } } describe("server session", () => { test("resolves lineage by session ID without directory", async () => { const ctx = setup({ child: session("child", "root"), root: session("root") }) const result = await ctx.store.lineage.resolve("child") expect(result.root.id).toBe("root") expect(ctx.get).toEqual([{ sessionID: "child" }, { sessionID: "root" }]) expect(ctx.store.lineage.peek("child")).toEqual(result) }) test("loads session content through the server client", async () => { const ctx = setup({ root: session("root") }) await ctx.store.sync("root") expect(ctx.get).toEqual([{ sessionID: "root" }]) expect(ctx.messages).toEqual([{ sessionID: "root", limit: 20, before: undefined }]) expect(ctx.store.data.message.root).toEqual([]) }) test("backfills an assistant-only initial page through its user root", async () => { const user = userMessage("message-1") const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)] const client = rootMessageClient( [ response( assistants.map((info) => ({ info, parts: [] })), "older", ), ], [singleResponse(user)], ) const store = createServerSession(client) await store.sync("child") expect(client.requests).toEqual([{ sessionID: "child", limit: 20, before: undefined }]) expect(client.rootRequests).toEqual([{ sessionID: "child", messageID: user.id }]) expect(store.data.message.child).toEqual([user, ...assistants]) expect(store.history.more("child")).toBe(true) }) test("keeps assistant history when its deleted parent cannot be backfilled", async () => { const missing = Promise.withResolvers<SingleMessageResponse>() const assistant = assistantMessage("message-2", "message-missing") const client = rootMessageClient([response([{ info: assistant, parts: [] }], "older")], [missing.promise]) const store = createServerSession(client) const loading = store.sync("child") await client.rootRequested(1) missing.reject(new Error("Message not found: message-missing", { cause: { status: 404 } })) await loading expect(client.rootRequests).toEqual([{ sessionID: "child", messageID: "message-missing" }]) expect(store.data.message.child).toEqual([assistant]) expect(store.history.more("child")).toBe(true) }) test("drops a cached parent when a forced refresh confirms it was deleted", async () => { const missing = Promise.withResolvers<SingleMessageResponse>() const parent = userMessage("message-1") const part = textPart(parent.id) const assistant = assistantMessage("message-2", parent.id) const client = rootMessageClient( [ response([ { info: parent, parts: [part] }, { info: assistant, parts: [] }, ]), response([{ info: assistant, parts: [] }], "older"), ], [missing.promise], ) const store = createServerSession(client) await store.sync("child") const loading = store.sync("child", { force: true }) await client.rootRequested(1) missing.reject(new Error(`Message not found: ${parent.id}`, { cause: { status: 404 } })) await loading expect(store.data.message.child).toEqual([assistant]) expect(store.data.part[parent.id]).toBeUndefined() }) test("does not let an optimistic user suppress initial root backfill", async () => { const user = userMessage("message-1") const part = textPart(user.id) const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)] const client = rootMessageClient( [ response( assistants.map((info) => ({ info, parts: [] })), "older", ), ], [singleResponse(user)], ) const store = createServerSession(client) store.optimistic.add({ sessionID: "child", message: user, parts: [part] }) await store.sync("child") store.optimistic.remove({ sessionID: "child", messageID: user.id }) expect(client.requests).toHaveLength(1) expect(client.rootRequests).toHaveLength(1) expect(store.data.message.child).toEqual([user, ...assistants]) }) test("backfills the parent of fetched assistants when another user is cached", async () => { const unrelated = userMessage("message-0", { time: { created: 0 } }) const user = userMessage("message-1") const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)] const client = rootMessageClient( [ response([{ info: unrelated, parts: [] }]), response( assistants.map((info) => ({ info, parts: [] })), "older", ), ], [singleResponse(user)], ) const store = createServerSession(client) await store.sync("child") await store.sync("child", { force: true }) expect(client.requests).toHaveLength(2) expect(client.rootRequests).toHaveLength(1) expect(store.data.message.child).toEqual([unrelated, user, ...assistants]) }) test("preserves cached history between an injected parent and the page boundary", async () => { const user = userMessage("message-1") const cached = userMessage("message-3", { time: { created: 3 } }) const assistant = assistantMessage("message-4", user.id) const client = rootMessageClient( [response([{ info: cached, parts: [] }]), response([{ info: assistant, parts: [] }], "older")], [singleResponse(user)], ) const store = createServerSession(client) await store.sync("child") await store.sync("child", { force: true }) expect(store.data.message.child).toEqual([user, cached, assistant]) }) test("refreshes a cached parent omitted by an assistant-only replacement page", async () => { const stale = userMessage("message-1", { summary: { title: "stale", diffs: [] } }) const fresh = { ...stale, summary: { title: "fresh", diffs: [] } } const stalePart = textPart(stale.id, { text: "stale" }) const freshPart = { ...stalePart, text: "fresh" } const assistant = assistantMessage("message-2", stale.id) const client = rootMessageClient( [response([{ info: stale, parts: [stalePart] }]), response([{ info: assistant, parts: [] }], "older")], [singleResponse(fresh, [freshPart])], ) const store = createServerSession(client) await store.sync("child") await store.sync("child", { force: true }) expect(client.rootRequests).toEqual([{ sessionID: "child", messageID: stale.id }]) expect(store.data.message.child).toEqual([fresh, assistant]) expect(store.data.part[stale.id]).toEqual([freshPart]) }) test("refreshes a confirmed optimistic parent while preserving pending parts", async () => { const stale = userMessage("message-1", { summary: { title: "stale", diffs: [] } }) const fresh = { ...stale, summary: { title: "fresh", diffs: [] } } const confirmed = textPart(stale.id, { id: "confirmed", text: "stale" }) const refreshed = { ...confirmed, text: "fresh" } const pending = textPart(stale.id, { id: "pending", text: "pending" }) const assistant = assistantMessage("message-2", stale.id) const client = rootMessageClient( [response([{ info: stale, parts: [confirmed] }]), response([{ info: assistant, parts: [] }], "older")], [singleResponse(fresh, [refreshed])], ) const store = createServerSession(client) store.optimistic.add({ sessionID: "child", message: stale, parts: [confirmed, pending] }) await store.sync("child") await store.sync("child", { force: true }) expect(client.rootRequests).toEqual([{ sessionID: "child", messageID: stale.id }]) expect(store.data.message.child).toEqual([fresh, assistant]) expect(store.data.part[stale.id]).toEqual([refreshed, pending]) }) test("uses a parent received by SSE during the replacement load", async () => { const pending = deferredResponse() const user = userMessage("message-1") const assistant = assistantMessage("message-2", user.id) const client = rootMessageClient([pending.promise], []) const store = createServerSession(client) const loading = store.sync("child") store.apply({ type: "message.updated", properties: { info: user } }) pending.resolve(response([{ info: assistant, parts: [] }], "older")) await loading expect(client.rootRequests).toEqual([]) expect(store.data.message.child).toEqual([user, assistant]) }) test("uses a successful retry over events received by a failed backfill attempt", async () => { const failed = deferredResponse() const user = userMessage("message-1") const live = { ...user, agent: "stale" } const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)] const client = rootMessageClient( [ response( assistants.map((info) => ({ info, parts: [] })), "older", ), ], [failed.promise.then((result) => ({ data: result.data[0]! })), singleResponse(user)], ) const store = createServerSession(client, { retry: retryImmediately }) const loading = store.sync("child") await client.rootRequested(1) store.apply({ type: "message.updated", properties: { info: live } }) failed.reject(new Error("retry")) await loading expect(client.requests).toHaveLength(1) expect(client.rootRequests).toHaveLength(2) expect(store.data.message.child).toEqual([user, ...assistants]) }) test("preserves newer-page events across a failed parent retry", async () => { const failed = deferredResponse() const user = userMessage("message-1") const assistant = assistantMessage("message-2", user.id) const live = { ...assistant, cost: 1 } const client = rootMessageClient( [response([{ info: assistant, parts: [] }], "older")], [failed.promise.then((result) => ({ data: result.data[0]! })), singleResponse(user)], ) const store = createServerSession(client, { retry: retryImmediately }) const loading = store.sync("child") await client.rootRequested(1) store.apply({ type: "message.updated", properties: { info: live } }) failed.reject(new Error("retry")) await loading expect(store.data.message.child).toEqual([user, live]) }) test("preserves unrelated message events across a failed parent retry", async () => { const failed = deferredResponse() const user = userMessage("message-1") const assistant = assistantMessage("message-2", user.id) const live = userMessage("message-4", { time: { created: 4 } }) const client = rootMessageClient( [response([{ info: assistant, parts: [] }], "older")], [failed.promise.then((result) => ({ data: result.data[0]! })), singleResponse(user)], ) const store = createServerSession(client, { retry: retryImmediately }) const loading = store.sync("child") await client.rootRequested(1) store.apply({ type: "message.updated", properties: { info: live } }) failed.reject(new Error("retry")) await loading expect(store.data.message.child).toEqual([user, assistant, live]) }) test("preserves newer-page part events across a failed parent retry", async () => { const failed = deferredResponse() const user = userMessage("message-1") const assistant = assistantMessage("message-2", user.id) const stale = textPart(assistant.id, { text: "stale" }) const live = { ...stale, text: "live" } const client = rootMessageClient( [response([{ info: assistant, parts: [stale] }], "older")], [failed.promise.then((result) => ({ data: result.data[0]! })), singleResponse(user)], ) const store = createServerSession(client, { retry: retryImmediately }) const loading = store.sync("child") await client.rootRequested(1) store.apply({ type: "message.part.updated", properties: { sessionID: "child", part: live, time: 2 } }) failed.reject(new Error("retry")) await loading expect(store.data.part[assistant.id]).toEqual([live]) }) test("merges live events into the initial page", async () => { const pending = deferredResponse() const user = userMessage("message-1") const live = userMessage("message-2", { time: { created: 2 } }) const livePart = textPart(live.id, { text: "live" }) const store = createServerSession(messageClient(pending.promise)) const loading = store.sync("child") store.apply({ type: "message.updated", properties: { info: live } }) store.apply({ type: "message.part.updated", properties: { sessionID: "child", part: livePart, time: 2 } }) pending.resolve(response([{ info: user, parts: [] }])) await loading expect(store.data.message.child).toEqual([user, live]) expect(store.data.part[live.id]).toEqual([livePart]) }) test("preserves same-ID live updates over the initial page", async () => { const pending = deferredResponse() const fetched = userMessage("message") const fetchedPart = textPart(fetched.id, { text: "fetched" }) const live = { ...fetched, time: { created: 2 } } const livePart = { ...fetchedPart, text: "live" } const store = createServerSession(messageClient(pending.promise)) const loading = store.sync("child") store.apply({ type: "message.updated", properties: { info: live } }) store.apply({ type: "message.part.updated", properties: { sessionID: "child", part: livePart, time: 2 } }) pending.resolve(response([{ info: fetched, parts: [fetchedPart] }])) await loading expect(store.data.message.child).toEqual([live]) expect(store.data.part[live.id]).toEqual([livePart]) }) test("preserves removals received during the initial load", async () => { const pending = deferredResponse() const removed = userMessage("message-1") const kept = { ...removed, id: "message-2" } const part = textPart(kept.id, { text: "removed" }) const store = createServerSession(messageClient(pending.promise)) const loading = store.sync("child") store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: removed.id } }) store.apply({ type: "message.part.removed", properties: { sessionID: "child", messageID: kept.id, partID: part.id }, }) pending.resolve( response([ { info: removed, parts: [] }, { info: kept, parts: [part] }, ]), ) await loading expect(store.data.message.child).toEqual([kept]) expect(store.data.part[kept.id]).toBeUndefined() }) test("keeps removal tracking isolated across load generations", async () => { const firstResponse = deferredResponse() const secondResponse = deferredResponse() const message = userMessage("message") const store = createServerSession(messageClient(firstResponse.promise, secondResponse.promise)) const first = store.sync("child") store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) store.apply({ type: "session.deleted", properties: { sessionID: "child", info: session("child", "root") }, }) const second = store.sync("child") firstResponse.resolve(response()) await first secondResponse.resolve(response([{ info: message, parts: [] }])) await second expect(store.data.message.child).toEqual([message]) }) test("tracks removals in a replacement load generation", async () => { const firstResponse = deferredResponse() const secondResponse = deferredResponse() const message = userMessage("message") const store = createServerSession(messageClient(firstResponse.promise, secondResponse.promise)) const first = store.sync("child") store.apply({ type: "session.deleted", properties: { sessionID: "child", info: session("child", "root") }, }) const second = store.sync("child") store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) firstResponse.resolve(response()) await first secondResponse.resolve(response([{ info: message, parts: [] }])) await second expect(store.data.message.child).toEqual([]) }) test("preserves remove then re-add when a refresh omits the message", async () => { const pending = deferredResponse() const message = userMessage("message") const store = createServerSession(messageClient(response([{ info: message, parts: [] }]), pending.promise)) await store.sync("child") const refreshing = store.sync("child", { force: true }) store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) store.apply({ type: "message.updated", properties: { info: message } }) pending.resolve(response()) await refreshing expect(store.data.message.child).toEqual([message]) }) test("preserves a re-added message without restoring removed parts", async () => { const pending = deferredResponse() const message = userMessage("message") const part = textPart(message.id, { text: "stale" }) const store = createServerSession(messageClient(response([{ info: message, parts: [] }]), pending.promise)) await store.sync("child") const refreshing = store.sync("child", { force: true }) store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) store.apply({ type: "message.updated", properties: { info: message } }) pending.resolve(response([{ info: message, parts: [part] }])) await refreshing expect(store.data.message.child).toEqual([message]) expect(store.data.part[message.id]).toBeUndefined() }) test("preserves optimistic parts re-added after removal during a refresh", async () => { const pending = deferredResponse() const message = userMessage("message") const stale = textPart(message.id, { id: "stale", text: "stale" }) const part = textPart(message.id, { id: "optimistic", text: "optimistic" }) const store = createServerSession( messageClient(response([{ info: message, parts: [] }]), pending.promise, response()), ) await store.sync("child") const refreshing = store.sync("child", { force: true }) store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) store.optimistic.add({ sessionID: "child", message, parts: [part] }) pending.resolve(response([{ info: message, parts: [stale] }])) await refreshing expect(store.data.message.child).toEqual([message]) expect(store.data.part[message.id]).toEqual([part]) await store.sync("child", { force: true }) expect(store.data.message.child).toEqual([message]) expect(store.data.part[message.id]).toEqual([part]) }) test("drops stale event content omitted by a complete initial page", async () => { const stale = userMessage("stale") const store = createServerSession(messageClient(response())) store.apply({ type: "message.updated", properties: { info: stale } }) await store.sync("child") expect(store.data.message.child).toEqual([]) }) test("preserves event content outside an incomplete initial page", async () => { const live = userMessage("message-1") const fetched = userMessage("message-2", { time: { created: 2 } }) const store = createServerSession(messageClient(response([{ info: fetched, parts: [] }], "older"))) store.apply({ type: "message.updated", properties: { info: live } }) await store.sync("child") expect(store.data.message.child).toEqual([live, fetched]) }) test("does not restore removed optimistic content on refresh", async () => { const message = userMessage("message") const part = textPart(message.id, { text: "removed" }) const kept = { ...message, id: "kept" } const keptPart = { ...part, id: "kept-part", messageID: kept.id } const store = createServerSession(messageClient(response([{ info: kept, parts: [] }]))) store.optimistic.add({ sessionID: "child", message, parts: [part] }) store.optimistic.add({ sessionID: "child", message: kept, parts: [keptPart] }) store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) store.apply({ type: "message.part.removed", properties: { sessionID: "child", messageID: kept.id, partID: keptPart.id }, }) await store.sync("child", { force: true }) expect(store.data.message.child).toEqual([kept]) expect(store.data.part[message.id]).toBeUndefined() expect(store.data.part[kept.id]).toBeUndefined() }) test("replaces confirmed optimistic content with the initial page", async () => { const optimistic = userMessage("message") const fetched = { ...optimistic, time: { created: 2 } } const store = createServerSession(messageClient(response([{ info: fetched, parts: [] }]))) store.optimistic.add({ sessionID: "child", message: optimistic, parts: [] }) await store.sync("child") expect(store.data.message.child).toEqual([fetched]) }) test("replaces a confirmed optimistic part with fetched content", async () => { const pending = deferredResponse() const message = userMessage("message") const optimistic = textPart(message.id, { text: "optimistic" }) const fetched = { ...optimistic, text: "fetched" } const store = createServerSession(messageClient(pending.promise)) const loading = store.sync("child") store.optimistic.add({ sessionID: "child", message, parts: [optimistic] }) pending.resolve(response([{ info: message, parts: [fetched] }])) await loading expect(store.data.part[message.id]).toEqual([fetched]) }) test("rolls back only unconfirmed optimistic parts", async () => { const pending = deferredResponse() const message = userMessage("message") const confirmed = textPart(message.id, { id: "confirmed", text: "confirmed" }) const pendingPart = textPart(message.id, { id: "pending", text: "pending" }) const store = createServerSession(messageClient(pending.promise)) const loading = store.sync("child") store.optimistic.add({ sessionID: "child", message, parts: [confirmed, pendingPart] }) pending.resolve(response([{ info: message, parts: [confirmed] }])) await loading store.optimistic.remove({ sessionID: "child", messageID: message.id }) expect(store.data.message.child).toEqual([message]) expect(store.data.part[message.id]).toEqual([confirmed]) }) test("updates confirmed optimistic parts from later pages", async () => { const message = userMessage("message") const confirmed = textPart(message.id, { id: "confirmed", text: "first" }) const updated = { ...confirmed, text: "updated" } const pendingPart = textPart(message.id, { id: "pending", text: "pending" }) const store = createServerSession( messageClient(response([{ info: message, parts: [confirmed] }]), response([{ info: message, parts: [updated] }])), ) store.optimistic.add({ sessionID: "child", message, parts: [confirmed, pendingPart] }) await store.sync("child") await store.sync("child", { force: true }) store.optimistic.remove({ sessionID: "child", messageID: message.id }) expect(store.data.part[message.id]).toEqual([updated]) }) test("does not restore a confirmed optimistic part after its removal event", async () => { const message = userMessage("message") const confirmed = textPart(message.id, { id: "confirmed", text: "confirmed" }) const pendingPart = textPart(message.id, { id: "pending", text: "pending" }) const store = createServerSession( messageClient(response([{ info: message, parts: [confirmed] }]), response([{ info: message, parts: [] }])), ) store.optimistic.add({ sessionID: "child", message, parts: [confirmed, pendingPart] }) await store.sync("child") store.apply({ type: "message.part.removed", properties: { sessionID: "child", messageID: message.id, partID: confirmed.id }, }) await store.sync("child", { force: true }) expect(store.data.part[message.id]).toEqual([pendingPart]) }) test("clears delta buffers when removing optimistic content", () => { const message = userMessage("message") const part = textPart(message.id, { text: "optimistic" }) const store = setup({ child: session("child") }).store store.optimistic.add({ sessionID: "child", message, parts: [part] }) store.apply({ type: "message.part.delta", properties: { sessionID: "child", messageID: message.id, partID: part.id, field: "text", delta: " delta" }, }) store.optimistic.remove({ sessionID: "child", messageID: message.id }) expect(store.data.part[message.id]).toBeUndefined() expect(store.data.part_text_accum_delta[part.id]).toBeUndefined() }) test("does not remove content confirmed by a message event", () => { const message = userMessage("message") const part = textPart(message.id) const store = setup({ child: session("child") }).store store.optimistic.add({ sessionID: "child", message, parts: [part] }) store.apply({ type: "message.updated", properties: { sessionID: "child", info: message } }) store.optimistic.remove({ sessionID: "child", messageID: message.id }) expect(store.data.message.child).toEqual([message]) expect(store.data.part[message.id]).toBeUndefined() }) test("does not remove parts confirmed by part events", () => { const message = userMessage("message") const part = textPart(message.id) const store = setup({ child: session("child") }).store store.optimistic.add({ sessionID: "child", message, parts: [part] }) store.apply({ type: "message.updated", properties: { sessionID: "child", info: message } }) store.apply({ type: "message.part.updated", properties: { sessionID: "child", part, time: 2 } }) store.optimistic.remove({ sessionID: "child", messageID: message.id }) expect(store.data.message.child).toEqual([message]) expect(store.data.part[message.id]).toEqual([part]) }) test("treats a part event as confirmation when it precedes the message event", () => { const message = userMessage("message") const part = textPart(message.id) const store = setup({ child: session("child") }).store store.optimistic.add({ sessionID: "child", message, parts: [part] }) store.apply({ type: "message.part.updated", properties: { sessionID: "child", part, time: 2 } }) store.optimistic.remove({ sessionID: "child", messageID: message.id }) expect(store.data.message.child).toEqual([message]) expect(store.data.part[message.id]).toEqual([part]) }) test("clears stale parts when the initial page has none", async () => { const pending = deferredResponse() const message = userMessage("message") const part = textPart(message.id, { text: "stale" }) const store = createServerSession(messageClient(pending.promise)) store.apply({ type: "message.updated", properties: { info: message } }) store.apply({ type: "message.part.updated", properties: { sessionID: "child", part, time: 1 } }) const loading = store.sync("child") pending.resolve(response([{ info: message, parts: [] }])) await loading expect(store.data.part[message.id]).toBeUndefined() }) test("clears delta buffers for parts omitted by the initial page", async () => { const pending = deferredResponse() const message = userMessage("message") const kept = textPart(message.id, { id: "part-1", text: "kept" }) const removed: Part = { ...kept, id: "part-2", text: "removed" } const store = createServerSession(messageClient(pending.promise)) store.apply({ type: "message.updated", properties: { info: message } }) store.apply({ type: "message.part.updated", properties: { sessionID: "child", part: kept, time: 1 } }) store.apply({ type: "message.part.updated", properties: { sessionID: "child", part: removed, time: 1 } }) store.apply({ type: "message.part.delta", properties: { sessionID: "child", messageID: message.id, partID: removed.id, field: "text", delta: " delta" }, }) const loading = store.sync("child") pending.resolve(response([{ info: message, parts: [kept] }])) await loading expect(store.data.part[message.id]).toEqual([kept]) expect(store.data.part_text_accum_delta[removed.id]).toBeUndefined() }) test("clears a stale delta buffer when a refresh replaces its part", async () => { const message = userMessage("message") const stale = textPart(message.id, { text: "stale" }) const fetched = { ...stale, text: "fetched" } const store = createServerSession( messageClient(response([{ info: message, parts: [stale] }]), response([{ info: message, parts: [fetched] }])), ) await store.sync("child") store.apply({ type: "message.part.delta", properties: { sessionID: "child", messageID: message.id, partID: stale.id, field: "text", delta: " delta" }, }) await store.sync("child", { force: true }) expect(store.data.part[message.id]).toEqual([fetched]) expect(store.data.part_text_accum_delta[stale.id]).toBeUndefined() }) test("preserves a non-durable delta received before refresh", async () => { const message = userMessage("message") const part = textPart(message.id, { text: "stale" }) const store = createServerSession( messageClient(response([{ info: message, parts: [part] }]), response([{ info: message, parts: [{ ...part }] }])), ) await store.sync("child") store.apply({ type: "message.part.delta", properties: { sessionID: "child", messageID: message.id, partID: part.id, field: "text", delta: " delta" }, }) await store.sync("child", { force: true }) expect(store.data.part[message.id]).toEqual([{ ...part, text: "stale delta" }]) expect(store.data.part_text_accum_delta[part.id]).toBe("stale delta") }) test("accepts fetched text that intentionally replaces an accumulated prefix", async () => { const message = userMessage("message") const part = textPart(message.id, { text: "abc" }) const fetched = { ...part, text: "ab" } const store = createServerSession( messageClient(response([{ info: message, parts: [part] }]), response([{ info: message, parts: [fetched] }])), ) await store.sync("child") store.apply({ type: "message.part.delta", properties: { sessionID: "child", messageID: message.id, partID: part.id, field: "text", delta: "def" }, }) await store.sync("child", { force: true }) expect(store.data.part[message.id]).toEqual([fetched]) expect(store.data.part_text_accum_delta[part.id]).toBeUndefined() }) test("preserves an unpersisted delta suffix after partial server catch-up", async () => { const message = userMessage("message") const part = textPart(message.id, { text: "a" }) const fetched = { ...part, text: "ab" } const store = createServerSession( messageClient(response([{ info: message, parts: [part] }]), response([{ info: message, parts: [fetched] }])), ) await store.sync("child") store.apply({ type: "message.part.delta", properties: { sessionID: "child", messageID: message.id, partID: part.id, field: "text", delta: "bc" }, }) await store.sync("child", { force: true }) expect(store.data.part[message.id]).toEqual([{ ...part, text: "abc" }]) expect(store.data.part_text_accum_delta[part.id]).toBe("abc") }) test("clears delta state after exact server catch-up", async () => { const message = userMessage("message") const part = textPart(message.id, { text: "a" }) const fetched = { ...part, text: "ab" } const store = createServerSession( messageClient(response([{ info: message, parts: [part] }]), response([{ info: message, parts: [fetched] }])), ) await store.sync("child") store.apply({ type: "message.part.delta", properties: { sessionID: "child", messageID: message.id, partID: part.id, field: "text", delta: "b" }, }) await store.sync("child", { force: true }) expect(store.data.part[message.id]).toEqual([fetched]) expect(store.data.part_text_accum_delta[part.id]).toBeUndefined() }) test("uses the successful retry response over events from a failed attempt", async () => { const failed = Promise.withResolvers<MessageResponse>() const retried = Promise.withResolvers<MessageResponse>() const message = userMessage("message") const stale = textPart(message.id, { text: "stale" }) const intermediate = { ...stale, text: "intermediate" } const fetched = { ...stale, text: "fetched" } const client = messageClient(failed.promise, retried.promise) const store = createServerSession(client, { retry: retryImmediately }) store.apply({ type: "message.updated", properties: { info: message } }) store.apply({ type: "message.part.updated", properties: { sessionID: "child", part: stale, time: 1 } }) const loading = store.sync("child") store.apply({ type: "message.part.updated", properties: { sessionID: "child", part: intermediate, time: 2 } }) failed.reject(new Error("failed to fetch")) await client.requested(2) retried.resolve(response([{ info: message, parts: [fetched] }])) await loading expect(store.data.part[message.id]).toEqual([fetched]) }) test("preserves non-durable deltas across message retries", async () => { const failed = Promise.withResolvers<MessageResponse>() const retried = Promise.withResolvers<MessageResponse>() const message = userMessage("message") const part = textPart(message.id, { text: "stale" }) const client = messageClient(failed.promise, retried.promise) const store = createServerSession(client, { retry: retryImmediately }) store.apply({ type: "message.updated", properties: { info: message } }) store.apply({ type: "message.part.updated", properties: { sessionID: "child", part, time: 1 } }) const loading = store.sync("child") store.apply({ type: "message.part.delta", properties: { sessionID: "child", messageID: message.id, partID: part.id, field: "text", delta: " delta" }, }) failed.reject(new Error("failed to fetch")) await client.requested(2) retried.resolve(response([{ info: message, parts: [part] }])) await loading expect(store.data.part[message.id]).toEqual([{ ...part, text: "stale delta" }]) }) test("preserves part removals across message retries", async () => { const failed = Promise.withResolvers<MessageResponse>() const retried = Promise.withResolvers<MessageResponse>() const message = userMessage("message") const part = textPart(message.id) const client = messageClient(response([{ info: message, parts: [part] }]), failed.promise, retried.promise) const store = createServerSession(client, { retry: retryImmediately }) await store.sync("child") const loading = store.sync("child", { force: true }) store.apply({ type: "message.part.removed", properties: { sessionID: "child", messageID: message.id, partID: part.id }, }) failed.reject(new Error("failed to fetch")) await client.requested(3) retried.resolve(response([{ info: message, parts: [part] }])) await loading expect(store.data.part[message.id]).toBeUndefined() }) test("preserves message removals across message retries", async () => { const failed = Promise.withResolvers<MessageResponse>() const retried = Promise.withResolvers<MessageResponse>() const message = userMessage("message") const part = textPart(message.id) const client = messageClient(response([{ info: message, parts: [part] }]), failed.promise, retried.promise) const store = createServerSession(client, { retry: retryImmediately }) await store.sync("child") const loading = store.sync("child", { force: true }) store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) failed.reject(new Error("failed to fetch")) await client.requested(3) retried.resolve(response([{ info: message, parts: [part] }])) await loading expect(store.data.message.child).toEqual([]) expect(store.data.part[message.id]).toBeUndefined() }) test("preserves optimistic re-adds across message retries", async () => { const failed = Promise.withResolvers<MessageResponse>() const retried = Promise.withResolvers<MessageResponse>() const message = userMessage("message") const stale = textPart(message.id, { id: "stale", text: "stale" }) const optimistic = textPart(message.id, { id: "optimistic", text: "optimistic" }) const client = messageClient(response([{ info: message, parts: [stale] }]), failed.promise, retried.promise) const store = createServerSession(client, { retry: retryImmediately }) await store.sync("child") const loading = store.sync("child", { force: true }) store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) store.optimistic.add({ sessionID: "child", message, parts: [optimistic] }) failed.reject(new Error("failed to fetch")) await client.requested(3) retried.resolve(response([{ info: message, parts: [stale] }])) await loading expect(store.data.message.child).toEqual([message]) expect(store.data.part[message.id]).toEqual([optimistic]) }) test("accepts part omission from a successful retry after an earlier delta", async () => { const failed = Promise.withResolvers<MessageResponse>() const retried = Promise.withResolvers<MessageResponse>() const message = userMessage("message") const part = textPart(message.id) const client = messageClient(response([{ info: message, parts: [part] }]), failed.promise, retried.promise) const store = createServerSession(client, { retry: retryImmediately }) await store.sync("child") const loading = store.sync("child", { force: true }) store.apply({ type: "message.part.delta", properties: { sessionID: "child", messageID: message.id, partID: part.id, field: "text", delta: " delta" }, }) failed.reject(new Error("failed to fetch")) await client.requested(3) retried.resolve(response([{ info: message, parts: [] }])) await loading expect(store.data.part[message.id]).toBeUndefined() expect(store.data.part_text_accum_delta[part.id]).toBeUndefined() }) test("clears load-owned orphan parts when all retries fail", async () => { const first = Promise.withResolvers<MessageResponse>() const second = Promise.withResolvers<MessageResponse>() const third = Promise.withResolvers<MessageResponse>() const message = userMessage("message") const part = textPart(message.id) const client = messageClient(first.promise, second.promise, third.promise) const store = createServerSession(client, { retry: retryImmediately }) const loading = store.sync("child").catch((error) => error) store.apply({ type: "message.part.updated", properties: { sessionID: "child", part, time: 2 } }) first.reject(new Error("failed to fetch")) await client.requested(2) second.reject(new Error("failed to fetch")) await client.requested(3) third.reject(new Error("failed to fetch")) await loading expect(store.data.part[message.id]).toBeUndefined() }) test("preserves live updates during a forced refresh", async () => { const pending = deferredResponse() const stale = userMessage("message") const stalePart = textPart(stale.id, { text: "stale" }) const store = createServerSession(messageClient(response([{ info: stale, parts: [stalePart] }]), pending.promise)) await store.sync("child") const refreshing = store.sync("child", { force: true }) const live = { ...stale, time: { created: 2 } } store.apply({ type: "message.updated", properties: { info: live } }) store.apply({ type: "message.part.delta", properties: { sessionID: "child", messageID: stale.id, partID: stalePart.id, field: "text", delta: " live" }, }) pending.resolve(response([{ info: stale, parts: [stalePart] }])) await refreshing expect(store.data.message.child).toEqual([live]) expect(store.data.part[stale.id]).toEqual([{ ...stalePart, text: "stale live" }]) }) test("keeps fetched message metadata when only a part changes", async () => { const pending = deferredResponse() const stale = userMessage("message") const fetched = { ...stale, time: { created: 2 } } const part = textPart(stale.id, { text: "stale" }) const store = createServerSession(messageClient(response([{ info: stale, parts: [part] }]), pending.promise)) await store.sync("child") const refreshing = store.sync("child", { force: true }) store.apply({ type: "message.part.delta", properties: { sessionID: "child", messageID: stale.id, partID: part.id, field: "text", delta: " live" }, }) pending.resolve(response([{ info: fetched, parts: [part] }])) await refreshing expect(store.data.message.child).toEqual([fetched]) expect(store.data.part[stale.id]).toEqual([{ ...part, text: "stale live" }]) }) test("preserves a part update when a forced refresh omits its message", async () => { const pending = deferredResponse() const message = userMessage("message") const stale = textPart(message.id, { text: "stale" }) const live = { ...stale, text: "live" } const store = createServerSession(messageClient(response([{ info: message, parts: [stale] }]), pending.promise)) await store.sync("child") const refreshing = store.sync("child", { force: true }) store.apply({ type: "message.part.updated", properties: { sessionID: "child", part: live, time: 2 } }) pending.resolve(response()) await refreshing expect(store.data.message.child).toEqual([message]) expect(store.data.part[message.id]).toEqual([live]) }) test("ignores a late part update after its message is removed", async () => { const pending = deferredResponse() const message = userMessage("message") const part = textPart(message.id) const store = createServerSession(messageClient(pending.promise)) const loading = store.sync("child") store.apply({ type: "message.updated", properties: { info: message } }) store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) store.apply({ type: "message.part.updated", properties: { sessionID: "child", part, time: 2 } }) pending.resolve(response([{ info: message, parts: [part] }])) await loading expect(store.data.message.child).toEqual([]) expect(store.data.part[message.id]).toBeUndefined() }) test("ignores a late part update after a completed message removal", () => { const message = userMessage("message") const part = textPart(message.id) const store = setup({ child: session("child") }).store store.apply({ type: "message.updated", properties: { info: message } }) store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) store.apply({ type: "message.part.updated", properties: { sessionID: "child", part, time: 2 } }) expect(store.data.part[message.id]).toBeUndefined() }) test("does not restore a completed message removal from a stale refresh", async () => { const message = userMessage("message") const part = textPart(message.id) const store = createServerSession( messageClient(response([{ info: message, parts: [part] }]), response([{ info: message, parts: [part] }])), ) await store.sync("child") store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) await store.sync("child", { force: true }) expect(store.data.message.child).toEqual([]) expect(store.data.part[message.id]).toBeUndefined() }) test("does not restore a completed part removal from a stale refresh", async () => { const message = userMessage("message") const part = textPart(message.id) const store = createServerSession( messageClient(response([{ info: message, parts: [part] }]), response([{ info: message, parts: [part] }])), ) await store.sync("child") store.apply({ type: "message.part.removed", properties: { sessionID: "child", messageID: message.id, partID: part.id }, }) await store.sync("child", { force: true }) expect(store.data.part[message.id]).toBeUndefined() }) test("does not cache skipped optimistic parts", () => { const message = userMessage("message") const part = { id: "part", sessionID: "child", messageID: message.id, type: "step-start" as const } const store = setup({ child: session("child") }).store store.optimistic.add({ sessionID: "child", message, parts: [part] }) expect(store.data.part[message.id]).toEqual([]) }) test("clears stale delta buffers when replacing optimistic parts", () => { const message = userMessage("message") const stale = textPart(message.id, { id: "stale", text: "stale" }) const optimistic = textPart(message.id, { id: "optimistic", text: "optimistic" }) const store = setup({ child: session("child") }).store store.optimistic.add({ sessionID: "child", message, parts: [stale] }) store.apply({ type: "message.part.delta", properties: { sessionID: "child", messageID: message.id, partID: stale.id, field: "text", delta: " delta" }, }) store.optimistic.add({ sessionID: "child", message, parts: [optimistic] }) expect(store.data.part_text_accum_delta[stale.id]).toBeUndefined() expect(store.data.part_text_accum_delta[optimistic.id]).toBeUndefined() }) test("preserves removals during history prepend", async () => { const pending = deferredResponse() const latest = userMessage("message-2", { time: { created: 2 } }) const older = { ...latest, id: "message-1", time: { created: 1 } } const store = createServerSession(messageClient(response([{ info: latest, parts: [] }], "older"), pending.promise)) await store.sync("child") const loading = store.history.loadMore("child") store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: older.id } }) pending.resolve(response([{ info: older, parts: [] }])) await loading expect(store.data.message.child).toEqual([latest]) }) test("does not scan cached messages for user roots during history prepend", async () => { const guard = { active: false } const latest = new Proxy(userMessage("message-2", { time: { created: 2 } }), { get(target, property, receiver) { if (guard.active && property === "role") throw new Error("cached role accessed") return Reflect.get(target, property, receiver) }, }) const older = userMessage("message-1") const store = createServerSession( messageClient(response([{ info: latest, parts: [] }], "older"), response([{ info: older, parts: [] }])), ) await store.sync("child") guard.active = true await store.history.loadMore("child") expect(store.data.message.child).toEqual([older, latest]) }) test("preserves loaded history during an incomplete refresh", async () => { const older = userMessage("message-1") const latest = userMessage("message-2", { time: { created: 2 } }) const fresh = userMessage("message-3", { time: { created: 3 } }) const store = createServerSession( messageClient( response( [ { info: older, parts: [] }, { info: latest, parts: [] }, ], "older", ), response( [ { info: latest, parts: [] }, { info: fresh, parts: [] }, ], "older", ), ), ) await store.sync("child") await store.sync("child", { force: true }) expect(store.data.message.child).toEqual([older, latest, fresh]) }) test("drops stale recent messages omitted by an incomplete refresh", async () => { const third = userMessage("message-3", { time: { created: 3 } }) const fourth = userMessage("message-4", { time: { created: 4 } }) const stale = userMessage("message-5", { time: { created: 5 } }) const store = createServerSession( messageClient( response( [ { info: fourth, parts: [] }, { info: stale, parts: [] }, ], "older", ), response( [ { info: third, parts: [] }, { info: fourth, parts: [] }, ], "older", ), ), ) await store.sync("child") await store.sync("child", { force: true }) expect(store.data.message.child).toEqual([third, fourth]) }) test("uses message creation time for incomplete refresh boundaries", async () => { const older = userMessage("msg_z", { time: { created: 1 } }) const boundary = userMessage("msg_m", { time: { created: 2 } }) const stale = userMessage("msg_a", { time: { created: 3 } }) const store = createServerSession( messageClient( response( [ { info: older, parts: [] }, { info: stale, parts: [] }, ], "older", ), response([{ info: boundary, parts: [] }], "older"), ), ) await store.sync("child") await store.sync("child", { force: true }) expect(store.data.message.child).toEqual([boundary, older]) }) test("preserves a part update for a message being loaded from history", async () => { const pending = deferredResponse() const latest = userMessage("message-2", { time: { created: 2 } }) const older = userMessage("message-1") const stale = textPart(older.id, { text: "stale" }) const live = { ...stale, text: "live" } const store = createServerSession(messageClient(response([{ info: latest, parts: [] }], "older"), pending.promise)) await store.sync("child") const loading = store.history.loadMore("child") store.apply({ type: "message.part.updated", properties: { sessionID: "child", part: live, time: 2 } }) pending.resolve(response([{ info: older, parts: [stale] }])) await loading expect(store.data.part[older.id]).toEqual([live]) }) test("does not clear newer orphan parts after terminal history prepend", async () => { const pending = deferredResponse() const latest = userMessage("message-2", { time: { created: 2 } }) const older = userMessage("message-1") const newer = userMessage("message-3", { time: { created: 3 } }) const part = textPart(newer.id, { text: "live" }) const store = createServerSession(messageClient(response([{ info: latest, parts: [] }], "older"), pending.promise)) await store.sync("child") const loading = store.history.loadMore("child") store.apply({ type: "message.part.updated", properties: { sessionID: "child", part, time: 3 } }) pending.resolve(response([{ info: older, parts: [] }])) await loading store.apply({ type: "message.updated", properties: { sessionID: "child", info: newer } }) expect(store.data.part[newer.id]).toEqual([part]) }) test("accepts an authoritative history part after an earlier unknown-parent update", async () => { const pending = deferredResponse() const history = deferredResponse() const latest = userMessage("message-2", { time: { created: 2 } }) const older = userMessage("message-1") const part = textPart(older.id, { text: "live" }) const store = createServerSession(messageClient(pending.promise, history.promise)) const loading = store.sync("child") store.apply({ type: "message.part.updated", properties: { sessionID: "child", part, time: 2 } }) pending.resolve(response([{ info: latest, parts: [] }], "older")) await loading expect(store.data.part[older.id]).toEqual([part]) const loadingHistory = store.history.loadMore("child") history.resolve(response([{ info: older, parts: [{ ...part, text: "stale" }] }])) await loadingHistory expect(store.data.part[older.id]).toEqual([{ ...part, text: "stale" }]) }) test("preserves an unknown-parent part removal across pages", async () => { const initial = deferredResponse() const history = deferredResponse() const latest = userMessage("message-2", { time: { created: 2 } }) const older = userMessage("message-1") const part = textPart(older.id) const store = createServerSession(messageClient(initial.promise, history.promise)) const loading = store.sync("child") store.apply({ type: "message.part.removed", properties: { sessionID: "child", messageID: older.id, partID: part.id }, }) initial.resolve(response([{ info: latest, parts: [] }], "older")) await loading const loadingHistory = store.history.loadMore("child") history.resolve(response([{ info: older, parts: [part] }])) await loadingHistory expect(store.data.part[older.id]).toBeUndefined() }) test("clears orphaned parts when a refresh drops a message", async () => { const message = userMessage("message") const part = textPart(message.id, { text: "stale" }) const store = createServerSession(messageClient(response([{ info: message, parts: [part] }]), response())) await store.sync("child") store.apply({ type: "message.part.delta", properties: { sessionID: "child", messageID: message.id, partID: part.id, field: "text", delta: " delta" }, }) await store.sync("child", { force: true }) expect(store.data.message.child).toEqual([]) expect(store.data.part[message.id]).toBeUndefined() expect(store.data.part_text_accum_delta[part.id]).toBeUndefined() }) test("applies events without a directory store", () => { const ctx = setup({}) ctx.store.apply({ type: "session.created", properties: { sessionID: "root", info: session("root") } }) ctx.store.apply({ type: "session.status", properties: { sessionID: "root", status: { type: "busy" } } }) expect(ctx.store.get("root")?.directory).toBe("/repo") expect(ctx.store.data.session_working("root")).toBe(true) expect(ctx.get).toEqual([]) }) test("preserves pinned session content under server-wide cache pressure", () => { const ctx = setup({}) ctx.store.pin("active") ctx.store.optimistic.add({ sessionID: "active", message: { id: "message", sessionID: "active", role: "assistant", time: { created: 1 }, parentID: "parent", modelID: "model", providerID: "provider", mode: "build", agent: "agent", path: { cwd: "/repo", root: "/repo" }, cost: 0, tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, }, parts: [], }) for (let index = 0; index < 50; index++) { ctx.store.remember(session(`session-${index}`)) ctx.store.apply({ type: "session.status", properties: { sessionID: `session-${index}`, status: { type: "idle" } }, }) } expect(ctx.store.data.message.active?.map((message) => message.id)).toEqual(["message"]) expect(ctx.store.data.session_status["session-0"]).toBeUndefined() }) })