Make streaming more resilient in Duo Chat
What does this MR do and why?
Final MR in the chain: wires WorkflowStream into Duo Agentic Chat in place of stream_manager, and surfaces a recoverable error state when automatic reconnection is exhausted.
The service classes themselves land in the three MRs below — this one is the wiring and the UX.
Changes
duo_agentic_chat_state_manager.vue— consumesworkflowStreamFactoryinstead ofstream_manager.onSocketErrornow recognisesmax_retries_exceededand rendersConnectionErrorAlertrather than a generic failure;handleRetryConnection()clears the state and callsRetryableWorkflowStream.retry().ConnectionErrorAlert(new) — danger alert with a Retry button, shown once all automatic attempts are spent. Follows thecredits_exhausted_alert.vuepattern.event_hub.js— subscribes through the stream service.stream_worker.js— parses frames before posting, so consumers receive objects rather than raw JSON strings. UsesWS_CLOSE_NORMALinstead of a bare1000.workflow_utils.js— follow-on adjustments for the above.
Chain
| MR | Delivers | |
|---|---|---|
| 1 | !249095 (merged) | BufferedEventHub |
| 2 | !247787 (merged) | WorkflowStream |
| 3 | !249096 (merged) | RetryableWorkflowStream + factory |
| 4 | this MR | Wiring + retry UX |
How to validate locally
No setup is required. A dev-only mock at ee/app/assets/javascripts/ai/duo_agentic_chat/websocket/dws_mock_websocket.js wraps the real WebSocket and lets you synthesise events from the browser console, so chat still talks to your GDK and streams real replies. See its README for details on the tool itself. It is active whenever NODE_ENV is development or test, and becomes available once a socket has been opened.
- Open Duo Agentic Chat in your GDK.
- Send a prompt to open the socket.
- Open the browser console and use
gl.dwsMockSocket.
| Console call | Expected |
|---|---|
gl.dwsMockSocket.close(1006) |
Silent reconnect, no visible error |
gl.dwsMockSocket.close(1001) |
Silent reconnect, no visible error |
gl.dwsMockSocket.close(1013) |
"Already responding in another tab" state, no reconnect |
gl.dwsMockSocket.close(1008) |
Out-of-credits or billing-forbidden state, no reconnect |
gl.dwsMockSocket.close(1000) |
Stream stops quietly |
gl.dwsMockSocket.message('{"newCheckpoint":{"status":"running"}}') |
Delivers a frame, which resets the retry counter |
gl.dwsMockSocket.open() |
Synthesises an open event on the current socket |
gl.dwsMockSocket.queue({ close: 1006, times: 3 }) |
Answers the next 3 connections with a close |
gl.dwsMockSocket.clearQueue() |
Drops the queue, so connections reach the real server |
Reload the page for a fresh connection.
Exhausting the retries
Closing by hand four times does not work: each reconnect reaches the real Duo Workflow Service, gets a real reply, and any delivered frame resets the retry counter. Queue the answers for the reconnects instead:
gl.dwsMockSocket.queue({ close: 1006, times: 3 }); // answer the next 3 reconnects
gl.dwsMockSocket.close(1006); // start the cascadeMAX_WS_RETRIES is 3, so three reconnects are attempted and the fourth consecutive failure gives up and shows ConnectionErrorAlert. Delays are exponential with jitter: roughly 0.5-1s, then 1-2s, then 2-4s, so the whole cascade takes a few seconds.
Once the alert appears, the Retry button resets the counter and reconnects immediately, with no backoff. The queue is spent by then, so that reconnect reaches the real service and the chat carries on. gl.dwsMockSocket.clearQueue() drops anything still queued.
Watch the console for breadcrumbs: every close logs [duo-chat][stream] closed with the code, category, and whether it is retryable, and each reconnect attempt logs [duo-chat][stream] reconnecting with the trigger, consecutive-failure count, max, and delay.
gl.dwsMockSocket.error() is a no-op here, because the stream worker never assigns an onerror handler. Use close(1006) instead to reach the error path, which is also what a dropped connection looks like.
FYI: Reconnect button is not expanded and centered anymore
It appears like that in the video but I changed it to left aligned based on @nickleonard's suggestion.(I don't want to record the video again):
MR acceptance checklist
- Changelog trailer (
Changelog: added,EE: true) - Tests updated for the new error path
- i18n strings wrapped with
s__()and PO file updated - Demo video — to be attached by author
References
Make Duo Chat WebSocket stream more resilient a... (#605829 - closed)
