@@ -22,6 +22,33 @@ import { executeToolAndReport, waitForToolCompletion, waitForToolDecision } from
2222
2323const logger = createLogger ( 'CopilotSseHandlers' )
2424
25+ /**
26+ * When the Sim→Go stream is aborted, avoid starting server-side tool work and
27+ * unblock the Go async waiter with a terminal 499 completion.
28+ */
29+ function abortPendingToolIfStreamDead (
30+ toolCall : ToolCallState ,
31+ toolCallId : string ,
32+ options : OrchestratorOptions ,
33+ context : StreamingContext
34+ ) : boolean {
35+ if ( ! options . abortSignal ?. aborted && ! context . wasAborted ) {
36+ return false
37+ }
38+ toolCall . status = 'cancelled'
39+ toolCall . endTime = Date . now ( )
40+ markToolResultSeen ( toolCallId )
41+ markToolComplete ( toolCall . id , toolCall . name , 499 , 'Request aborted before tool execution' , {
42+ cancelled : true ,
43+ } ) . catch ( ( err ) => {
44+ logger . error ( 'markToolComplete fire-and-forget failed (stream aborted)' , {
45+ toolCallId : toolCall . id ,
46+ error : err instanceof Error ? err . message : String ( err ) ,
47+ } )
48+ } )
49+ return true
50+ }
51+
2552/**
2653 * Extract the `ui` object from a Go SSE event. The Go backend enriches
2754 * tool_call events with `ui: { requiresConfirmation, clientExecutable, ... }`.
@@ -321,7 +348,9 @@ export const sseHandlers: Record<string, SSEHandler> = {
321348
322349 if ( options . interactive === false ) {
323350 if ( options . autoExecuteTools !== false ) {
324- fireToolExecution ( )
351+ if ( ! abortPendingToolIfStreamDead ( toolCall , toolCallId , options , context ) ) {
352+ fireToolExecution ( )
353+ }
325354 }
326355 return
327356 }
@@ -345,7 +374,9 @@ export const sseHandlers: Record<string, SSEHandler> = {
345374 await emitSyntheticToolResult ( toolCallId , toolCall . name , completion , options )
346375 return
347376 }
348- fireToolExecution ( )
377+ if ( ! abortPendingToolIfStreamDead ( toolCall , toolCallId , options , context ) ) {
378+ fireToolExecution ( )
379+ }
349380 return
350381 }
351382
@@ -406,7 +437,9 @@ export const sseHandlers: Record<string, SSEHandler> = {
406437 // delegate to the client (React UI) and wait for completion.
407438 if ( clientExecutable ) {
408439 if ( isToolAvailableOnSimSide ( toolName ) ) {
409- fireToolExecution ( )
440+ if ( ! abortPendingToolIfStreamDead ( toolCall , toolCallId , options , context ) ) {
441+ fireToolExecution ( )
442+ }
410443 } else {
411444 toolCall . status = 'executing'
412445 const completion = await waitForToolCompletion (
@@ -421,7 +454,9 @@ export const sseHandlers: Record<string, SSEHandler> = {
421454 }
422455
423456 if ( options . autoExecuteTools !== false ) {
424- fireToolExecution ( )
457+ if ( ! abortPendingToolIfStreamDead ( toolCall , toolCallId , options , context ) ) {
458+ fireToolExecution ( )
459+ }
425460 }
426461 } ,
427462 reasoning : ( event , context ) => {
@@ -575,7 +610,9 @@ export const subAgentHandlers: Record<string, SSEHandler> = {
575610
576611 if ( options . interactive === false ) {
577612 if ( options . autoExecuteTools !== false ) {
578- fireToolExecution ( )
613+ if ( ! abortPendingToolIfStreamDead ( toolCall , toolCallId , options , context ) ) {
614+ fireToolExecution ( )
615+ }
579616 }
580617 return
581618 }
@@ -598,7 +635,9 @@ export const subAgentHandlers: Record<string, SSEHandler> = {
598635 await emitSyntheticToolResult ( toolCallId , toolCall . name , completion , options )
599636 return
600637 }
601- fireToolExecution ( )
638+ if ( ! abortPendingToolIfStreamDead ( toolCall , toolCallId , options , context ) ) {
639+ fireToolExecution ( )
640+ }
602641 return
603642 }
604643 if ( decision ?. status === 'rejected' || decision ?. status === 'error' ) {
@@ -655,7 +694,9 @@ export const subAgentHandlers: Record<string, SSEHandler> = {
655694
656695 if ( clientExecutable ) {
657696 if ( isToolAvailableOnSimSide ( toolName ) ) {
658- fireToolExecution ( )
697+ if ( ! abortPendingToolIfStreamDead ( toolCall , toolCallId , options , context ) ) {
698+ fireToolExecution ( )
699+ }
659700 } else {
660701 toolCall . status = 'executing'
661702 const completion = await waitForToolCompletion (
@@ -670,7 +711,9 @@ export const subAgentHandlers: Record<string, SSEHandler> = {
670711 }
671712
672713 if ( options . autoExecuteTools !== false ) {
673- fireToolExecution ( )
714+ if ( ! abortPendingToolIfStreamDead ( toolCall , toolCallId , options , context ) ) {
715+ fireToolExecution ( )
716+ }
674717 }
675718 } ,
676719 tool_result : ( event , context ) => {
0 commit comments