fix: handle ambiguous prompt transport failures

This commit is contained in:
Bohdan Triapitsyn
2026-07-07 19:49:11 +03:00
parent c801c7a74b
commit 40dfff4a9a
12 changed files with 456 additions and 100 deletions
+92 -1
View File
@@ -10,6 +10,7 @@ let sessionRevertResult: { data?: unknown; error?: unknown; response?: { status?
let questionReplyError: unknown | null = null
let questionRejectError: unknown | null = null
let sessionShareResult: { data?: unknown; error?: unknown; response?: { status?: number } } = {}
let sessionMessagesResult: { data?: unknown; error?: unknown; response?: { status?: number } } = { data: [] }
const globalUpsertedSessions: unknown[] = []
const mockScopedClient = {
@@ -41,7 +42,7 @@ const mockSdk = {
session: {
messages: mock((params: Record<string, unknown>) => {
replyCalls.push({ method: "session.messages", params })
return Promise.resolve({ data: [] })
return Promise.resolve(sessionMessagesResult)
}),
revert: mock((params: Record<string, unknown>) => {
replyCalls.push({ method: "session.revert", params })
@@ -343,6 +344,7 @@ describe("optimisticSend target directory", () => {
beforeEach(() => {
replyCalls.length = 0
scopedClientDirectories.length = 0
sessionMessagesResult = { data: [] }
})
test("passes the prompt directory to optimistic state during session switch races", async () => {
@@ -438,6 +440,95 @@ describe("optimisticSend target directory", () => {
expect((optimisticRemove as unknown as OptimisticRemoveCall).sessionID).toBe("session-race")
expect(targetStore.getState().session_status["session-race"]?.type).toBe("idle")
})
test("confirms an ambiguous send failure with a recent message refetch", async () => {
const targetStore = createStore({})
const childStores = createChildStores([["/target/project", targetStore]])
let optimisticRemove: OptimisticRemoveCall | null = null
let optimisticConfirm: OptimisticRemoveCall | null = null
let sentMessageID = ""
const { optimisticSend, setActionRefs, setOptimisticRefs } = await import("./session-actions")
setActionRefs(mockSdk as unknown as OpencodeClient, childStores, () => "/target/project")
setOptimisticRefs(
() => {},
(input) => {
optimisticRemove = input
},
(input) => {
optimisticConfirm = input
},
)
await optimisticSend({
sessionId: "session-confirmed",
directory: "/target/project",
content: "hello",
providerID: "provider",
modelID: "model",
send: async (messageID) => {
sentMessageID = messageID
sessionMessagesResult = {
data: [{
info: { id: messageID, role: "user", sessionID: "session-confirmed", time: { created: 1 } } as Message,
parts: [{ id: "server-part", type: "text", text: "hello" } as Part],
}],
}
const error = new Error("Failed to send message (504): gateway timeout") as Error & { status?: number }
error.status = 504
throw error
},
})
expect(optimisticRemove).toBe(null)
expect((optimisticConfirm as OptimisticRemoveCall | null)?.messageID).toBe(sentMessageID)
expect(replyCalls.find((call) => call.method === "session.messages")?.params.limit).toBe(30)
expect(targetStore.getState().message["session-confirmed"]?.[0]?.id).toBe(sentMessageID)
expect(targetStore.getState().part[sentMessageID]?.[0]?.id).toBe("server-part")
})
test("rolls back an ambiguous send failure when recent messages do not contain the sent ID", async () => {
const targetStore = createStore({})
const childStores = createChildStores([["/target/project", targetStore]])
let optimisticRemove: OptimisticRemoveCall | null = null
let optimisticConfirm: OptimisticRemoveCall | null = null
const { optimisticSend, setActionRefs, setOptimisticRefs } = await import("./session-actions")
setActionRefs(mockSdk as unknown as OpencodeClient, childStores, () => "/target/project")
setOptimisticRefs(
() => {},
(input) => {
optimisticRemove = input
},
(input) => {
optimisticConfirm = input
},
)
let caught: unknown = null
try {
await optimisticSend({
sessionId: "session-missing",
directory: "/target/project",
content: "hello",
providerID: "provider",
modelID: "model",
send: async () => {
const error = new Error("Failed to send message (504): gateway timeout") as Error & { status?: number }
error.status = 504
throw error
},
})
} catch (error) {
caught = error
}
expect(caught).toBeInstanceOf(Error)
expect((optimisticRemove as OptimisticRemoveCall | null)?.sessionID).toBe("session-missing")
expect(optimisticConfirm).toBe(null)
expect(replyCalls.filter((call) => call.method === "session.messages").every((call) => call.params.limit === 30)).toBe(true)
expect(targetStore.getState().session_status["session-missing"]?.type).toBe("idle")
})
})
describe("respondToPermission passes directory", () => {