@@ -153,35 +153,35 @@ def has_events(self) -> bool:
153153
154154
155155class WebSocketEmitter (StreamEmitter ):
156- """WebSocket emitter using Socket.IO namespace .
156+ """WebSocket emitter using global ws_emitter for cross-worker broadcasting .
157157
158- Emits events through a Socket.IO namespace to a specific room.
158+ Uses the global ws_emitter (with Redis adapter) to ensure events are
159+ broadcast to all backend replicas in multi-instance deployments.
159160 """
160161
161- def __init__ (self , namespace : Any , task_room : str ):
162+ def __init__ (self , namespace : Any , task_room : str , task_id : int ):
162163 """Initialize WebSocket emitter.
163164
164165 Args:
165- namespace: Socket.IO namespace instance
166+ namespace: Socket.IO namespace instance (kept for compatibility)
166167 task_room: Room name for broadcasting events
168+ task_id: Task ID for emitting events
167169 """
168170 self .namespace = namespace
169171 self .task_room = task_room
172+ self .task_id = task_id
170173
171174 async def emit_start (
172175 self , task_id : int , subtask_id : int , shell_type : str = "Chat"
173176 ) -> None :
174- """Emit chat:start event with shell_type."""
175- from app .api .ws .events import ServerEvents
176-
177- await self .namespace .emit (
178- ServerEvents .CHAT_START ,
179- {
180- "task_id" : task_id ,
181- "subtask_id" : subtask_id ,
182- "shell_type" : shell_type , # Include shell_type for frontend display logic
183- },
184- room = self .task_room ,
177+ """Emit chat:start event using global emitter with shell_type."""
178+ from app .services .chat .ws_emitter import get_ws_emitter
179+
180+ emitter = get_ws_emitter ()
181+ await emitter .emit_chat_start (
182+ task_id = task_id ,
183+ subtask_id = subtask_id ,
184+ shell_type = shell_type ,
185185 )
186186 logger .info ("[WS_EMITTER] chat:start emitted with shell_type=%s" , shell_type )
187187
@@ -192,11 +192,11 @@ async def emit_chunk(
192192 subtask_id : int ,
193193 result : dict [str , Any ] | None = None ,
194194 ) -> None :
195- """Emit chat:chunk event with optional result data (thinking, workbench) .
195+ """Emit chat:chunk event using global emitter with optional result data.
196196
197197 This follows the same pattern as executor tasks to enable thinking/workbench display.
198198 """
199- from app .api . ws . events import ServerEvents
199+ from app .services . chat . ws_emitter import get_ws_emitter
200200
201201 logger .debug (
202202 "[WS_EMITTER] emit_chunk: subtask_id=%d, offset=%d, content_len=%d, has_result=%s" ,
@@ -205,19 +205,13 @@ async def emit_chunk(
205205 len (content ),
206206 result is not None ,
207207 )
208- payload : dict [str , Any ] = {
209- "subtask_id" : subtask_id ,
210- "content" : content ,
211- "offset" : offset ,
212- }
213- # Include full result if provided (for chat_v2 thinking/workbench display)
214- if result is not None :
215- payload ["result" ] = result
216-
217- await self .namespace .emit (
218- ServerEvents .CHAT_CHUNK ,
219- payload ,
220- room = self .task_room ,
208+ emitter = get_ws_emitter ()
209+ await emitter .emit_chat_chunk (
210+ task_id = self .task_id ,
211+ subtask_id = subtask_id ,
212+ content = content ,
213+ offset = offset ,
214+ result = result ,
221215 )
222216
223217 async def emit_done (
@@ -228,40 +222,38 @@ async def emit_done(
228222 result : dict [str , Any ],
229223 message_id : int | None = None ,
230224 ) -> None :
231- """Emit chat:done event."""
232- from app .api .ws .events import ServerEvents
233-
234- await self .namespace .emit (
235- ServerEvents .CHAT_DONE ,
236- {
237- "task_id" : task_id ,
238- "subtask_id" : subtask_id ,
239- "offset" : offset ,
240- "result" : result ,
241- "message_id" : message_id ,
242- },
243- room = self .task_room ,
225+ """Emit chat:done event using global emitter."""
226+ from app .services .chat .ws_emitter import get_ws_emitter
227+
228+ emitter = get_ws_emitter ()
229+ await emitter .emit_chat_done (
230+ task_id = task_id ,
231+ subtask_id = subtask_id ,
232+ offset = offset ,
233+ result = result ,
234+ message_id = message_id ,
244235 )
245236 logger .info ("[WS_EMITTER] chat:done emitted message_id=%s" , message_id )
246237
247238 async def emit_error (self , subtask_id : int , error : str ) -> None :
248- """Emit chat:error event."""
249- from app .api .ws .events import ServerEvents
250-
251- await self .namespace .emit (
252- ServerEvents .CHAT_ERROR ,
253- {"subtask_id" : subtask_id , "error" : error },
254- room = self .task_room ,
239+ """Emit chat:error event using global emitter."""
240+ from app .services .chat .ws_emitter import get_ws_emitter
241+
242+ emitter = get_ws_emitter ()
243+ await emitter .emit_chat_error (
244+ task_id = self .task_id ,
245+ subtask_id = subtask_id ,
246+ error = error ,
255247 )
256248 logger .warning ("[WS_EMITTER] chat:error emitted: %s" , error )
257249
258250 async def emit_cancelled (self , subtask_id : int ) -> None :
259- """Emit chat:cancelled event."""
260- from app .api . ws . events import ServerEvents
251+ """Emit chat:cancelled event using global emitter ."""
252+ from app .services . chat . ws_emitter import get_ws_emitter
261253
262- await self . namespace . emit (
263- ServerEvents . CHAT_CANCELLED ,
264- { "subtask_id" : subtask_id } ,
265- room = self . task_room ,
254+ emitter = get_ws_emitter ()
255+ await emitter . emit_chat_cancelled (
256+ task_id = self . task_id ,
257+ subtask_id = subtask_id ,
266258 )
267259 logger .info ("[WS_EMITTER] chat:cancelled emitted" )
0 commit comments