Просмотр исходного кода

fix(app): tolerate missing message parents (#37918)

Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
opencode-agent[bot] 1 месяц назад
Родитель
Сommit
cc3615b535

+ 43 - 0
packages/app/src/context/server-session.test.ts

@@ -200,6 +200,49 @@ describe("server session", () => {
     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)

+ 12 - 1
packages/app/src/context/server-session.ts

@@ -110,6 +110,7 @@ function reconcileFetched<T extends { id: string }>(
   options: {
     touched?: ReadonlySet<string>
     retained?: ReadonlySet<string>
+    removed?: ReadonlySet<string>
     preserveUnfetched?: boolean | ((item: T) => boolean)
   } = {},
 ) {
@@ -132,6 +133,7 @@ function reconcileFetched<T extends { id: string }>(
     if (item) result.set(id, item)
     if (!item) result.delete(id)
   }
+  for (const id of options.removed ?? emptyIDs) result.delete(id)
   return [...result.values()].sort((a, b) => cmp(a.id, b.id))
 }
 
@@ -577,6 +579,7 @@ export function createServerSession(client: OpencodeClient, options?: { retry?:
     const messages = reconcileFetched(merged.session, data.message[sessionID] ?? [], {
       touched: touchedMessages,
       retained: load?.retainedMessages,
+      removed: load?.removedMessages,
       preserveUnfetched,
     })
     batch(() => {
@@ -645,7 +648,15 @@ export function createServerSession(client: OpencodeClient, options?: { retry?:
           if (generations.get(sessionID) !== active) break
           const parent = await fetchMessage(sessionID, parentID, () =>
             resetMessageLoad(sessionID, load, messageLoadBaseline(load, parentID)),
-          )
+          ).catch((error) => {
+            const cause = error instanceof Error && typeof error.cause === "object" ? error.cause : undefined
+            if (cause && "status" in cause && cause.status === 404) {
+              load.removedMessages.add(parentID)
+              return
+            }
+            throw error
+          })
+          if (!parent) continue
           if (parent.message.role !== "user") throw new Error(`Assistant parent is not a user message: ${parentID}`)
           parents.push(parent)
         }