Add WorkflowStream service for Duo Chat streaming
What does this MR do and why?
Second of three MRs splitting the Duo Agentic Chat WebSocket service layer into independently reviewable pieces. Depends on BufferedEventHub (MR 1). Nothing consumes this class yet.
stream_manager is a module-level singleton that conflates socket mechanics with lifecycle: callers cannot scope a stream to one workflow, and every consumer has to interpret raw close codes for itself.
WorkflowStream owns a single WebSocket connection through the stream worker and exposes it as a small state machine (IDLE → CONNECTING → OPENED → CLOSED | ERROR). Buffering and replay come from BufferedEventHub.
Close codes are categorised into { category, retryable } so consumers branch on intent rather than on numbers:
| Code | Category | Retryable |
|---|---|---|
| 1000 | normal |
No |
| 1001 | going_away |
Yes |
| 1008 | policy_violation |
No |
| 1013 | try_again_later |
No |
| 4400 | invalid_request |
No |
| 1006 / other | error |
Yes |
Unknown codes fall through to a retryable error rather than being treated as a clean shutdown — that fallthrough is what makes a dropped connection (1006) recoverable in MR 3.
Second commit: gl.dwsMockSocket
exposeMockSocket moves out of stream_manager.js into dws_mock_websocket.js, so both stream_manager and WorkflowStream share one implementation instead of duplicating it.
It now takes a getter rather than a worker instance. Both callers null their worker on terminate(), and resolving per call keeps the console helpers no-ops afterwards instead of posting to a worker that is already gone. Behaviour for stream_manager is unchanged, which is why its specs are untouched — they are the regression guard.
Whichever module creates a worker last owns the global. stream_manager is the live consumer today; WorkflowStream takes over in MR 4.
Chain
| MR | Delivers | |
|---|---|---|
| 1 | !249095 (merged) | BufferedEventHub |
| 2 | this MR | WorkflowStream |
| 3 | !249096 (merged) | RetryableWorkflowStream + factory |
| 4 | !245705 (merged) | Wiring + retry UX |
How to validate
yarn jest ee/spec/frontend/ai/duo_agentic_chat/websocket/