@@ -14,6 +14,18 @@ import { env } from '@/lib/core/config/env'
1414
1515const logger = createLogger ( 'CopilotChatStreaming' )
1616
17+ // Registry of in-flight Sim→Go streams so the explicit abort endpoint can
18+ // reach them. Keyed by streamId, cleaned up when the stream completes.
19+ const activeStreams = new Map < string , AbortController > ( )
20+
21+ export function abortActiveStream ( streamId : string ) : boolean {
22+ const controller = activeStreams . get ( streamId )
23+ if ( ! controller ) return false
24+ controller . abort ( )
25+ activeStreams . delete ( streamId )
26+ return true
27+ }
28+
1729const FLUSH_EVENT_TYPES = new Set ( [
1830 'tool_call' ,
1931 'tool_result' ,
@@ -94,6 +106,7 @@ export function createSSEStream(params: StreamingOrchestrationParams): ReadableS
94106 let eventWriter : ReturnType < typeof createStreamEventWriter > | null = null
95107 let clientDisconnected = false
96108 const abortController = new AbortController ( )
109+ activeStreams . set ( streamId , abortController )
97110
98111 return new ReadableStream ( {
99112 async start ( controller ) {
@@ -163,8 +176,14 @@ export function createSSEStream(params: StreamingOrchestrationParams): ReadableS
163176 await eventWriter . close ( )
164177 await setStreamMeta ( streamId , { status : 'complete' , userId } )
165178 } catch ( error ) {
166- if ( clientDisconnected || abortController . signal . aborted ) {
167- logger . info ( `[${ requestId } ] Stream aborted by client disconnect` )
179+ if ( abortController . signal . aborted ) {
180+ logger . info ( `[${ requestId } ] Stream aborted by explicit stop` )
181+ await eventWriter . close ( ) . catch ( ( ) => { } )
182+ await setStreamMeta ( streamId , { status : 'complete' , userId } )
183+ return
184+ }
185+ if ( clientDisconnected ) {
186+ logger . info ( `[${ requestId } ] Stream ended after client disconnect` )
168187 await eventWriter . close ( ) . catch ( ( ) => { } )
169188 await setStreamMeta ( streamId , { status : 'complete' , userId } )
170189 return
@@ -183,14 +202,18 @@ export function createSSEStream(params: StreamingOrchestrationParams): ReadableS
183202 } ,
184203 } )
185204 } finally {
186- controller . close ( )
205+ activeStreams . delete ( streamId )
206+ try {
207+ controller . close ( )
208+ } catch {
209+ // Controller already closed from cancel() — safe to ignore
210+ }
187211 }
188212 } ,
189- async cancel ( ) {
213+ cancel ( ) {
190214 clientDisconnected = true
191- abortController . abort ( )
192215 if ( eventWriter ) {
193- await eventWriter . flush ( )
216+ eventWriter . flush ( ) . catch ( ( ) => { } )
194217 }
195218 } ,
196219 } )
0 commit comments