Improve event stream resilience

This commit is contained in:
Bohdan Triapitsyn
2026-04-24 12:30:11 +03:00
parent f0c3d5e326
commit 9d9af7e263
8 changed files with 240 additions and 2 deletions
@@ -667,6 +667,85 @@ describe('createEventPipeline', () => {
]);
});
it('passes the last websocket event id when falling back to SSE', async () => {
installDomStubs();
globalThis.WebSocket = FakeWebSocket;
const originalConsoleError = console.error;
console.error = () => {};
let releaseStream;
const hold = new Promise((resolve) => {
releaseStream = resolve;
});
const eventOptions = [];
const received = [];
const sdk = {
global: {
event: async (options) => {
eventOptions.push(options);
return {
stream: (async function* () {
yield {
payload: {
type: 'server.connected',
properties: {},
},
};
await hold;
})(),
};
},
},
};
const delivered = new Promise((resolve) => {
const { cleanup } = createEventPipeline({
sdk,
transport: 'auto',
reconnectDelayMs: 0,
wsReadyTimeoutMs: 20,
onEvent: (directory, payload) => {
received.push({ directory, payload });
if (payload.type !== 'server.connected') {
return;
}
cleanup();
releaseStream();
resolve();
},
});
});
try {
await Promise.resolve();
const firstSocket = FakeWebSocket.instances[0];
firstSocket.emitOpen();
firstSocket.emitMessage({ type: 'ready', scope: 'global' });
firstSocket.emitMessage({
type: 'event',
eventId: 'evt-1',
directory: '/tmp/project',
payload: {
type: 'session.status',
properties: {
sessionID: 'session-1',
},
},
});
firstSocket.emitClose();
await new Promise((resolve) => setTimeout(resolve, 40));
await delivered;
expect(eventOptions[0]?.headers?.['Last-Event-ID']).toBe('evt-1');
expect(received.some((entry) => entry.payload.type === 'server.connected')).toBe(true);
} finally {
console.error = originalConsoleError;
}
});
it('marks the pipeline disconnected on heartbeat timeout and recovers on the next websocket connect', async () => {
installDomStubs();
globalThis.WebSocket = FakeWebSocket;
+1
View File
@@ -339,6 +339,7 @@ export function createEventPipeline(input: EventPipelineInput) {
const runSseAttempt = async (signal: AbortSignal) => {
const events = await sdk.global.event({
signal,
...(lastEventId && lastEventId.length > 0 ? { headers: { "Last-Event-ID": lastEventId } } : {}),
onSseError: (error: unknown) => {
if (isAbortError(error)) return
if (streamErrorLogged) return