fix: reconcile stale queued messages (#1063)

Recover missed messages-consumed events from authoritative Hub state after session SSE reconnects.
This commit is contained in:
Shawn Tian
2026-07-18 12:18:47 +08:00
committed by GitHub
parent 22e9b38f70
commit d809fca433
17 changed files with 704 additions and 10 deletions
+23
View File
@@ -92,4 +92,27 @@ describe('ApiClient error mapping', () => {
})
expect(fetchMock.mock.calls[0]?.[0]).toBe('/api/sessions/session%20cursor/cursor-chat-store')
})
it('loads the authoritative queued state for encoded session IDs', async () => {
fetchMock.mockResolvedValueOnce(
new Response(JSON.stringify({
queuedLocalIds: ['local-2'],
invokedLocalMessages: [{ localId: 'local-3', invokedAt: 1_000 }]
}), { status: 200 })
)
const api = new ApiClient('test-token')
await expect(api.getQueuedState('session /?#', ['local-1', 'local-2'])).resolves.toEqual({
queuedLocalIds: ['local-2'],
invokedLocalMessages: [{ localId: 'local-3', invokedAt: 1_000 }]
})
const [url, init] = fetchMock.mock.calls[0] ?? []
expect(url).toBe('/api/sessions/session%20%2F%3F%23/messages/queued-state')
expect(init).toMatchObject({
method: 'POST',
body: JSON.stringify({ localIds: ['local-1', 'local-2'] })
})
expect(new Headers(init?.headers).get('content-type')).toBe('application/json')
})
})
+11
View File
@@ -39,6 +39,7 @@ import type {
MachinePathsExistsResponse,
OpencodeModelsResponse,
OpencodeReasoningEffortResponse,
QueuedStateResponse,
ReopenSessionResponse,
UploadFileResponse
} from '@hapi/protocol/apiTypes'
@@ -408,6 +409,16 @@ export class ApiClient {
})
}
async getQueuedState(sessionId: string, localIds: string[]): Promise<QueuedStateResponse> {
return await this.request<QueuedStateResponse>(
`/api/sessions/${encodeURIComponent(sessionId)}/messages/queued-state`,
{
method: 'POST',
body: JSON.stringify({ localIds })
}
)
}
async cancelMessage(sessionId: string, messageId: string): Promise<CancelMessageResponse> {
const response = await this.request(
`/api/sessions/${encodeURIComponent(sessionId)}/messages/${encodeURIComponent(messageId)}`,