@@ -1439,7 +1439,8 @@ export function ChatPage(props: ChatPageProps) {
14391439 scrollAreaRef,
14401440 composerBusyRef,
14411441 } ) ;
1442- const { queueGatewayBridgeEventForRequest } = useGatewayBridgeBatcher ( ) ;
1442+ const { queueGatewayBridgeEventForRequest, flushGatewayBridgeEventsForRequest } =
1443+ useGatewayBridgeBatcher ( ) ;
14431444 const {
14441445 currentConversationIdRef,
14451446 conversationRuntimeCacheRef,
@@ -2295,13 +2296,29 @@ export function ChatPage(props: ChatPageProps) {
22952296 ) ;
22962297 inFlightQueuedTurn = null ;
22972298 } else if ( gatewayRequest ) {
2298- void invoke ( "gateway_chat_complete" , {
2299- request_id : gatewayRequest . requestId ,
2300- conversation_id : targetConversationId ,
2301- worker_id : gatewayWorkerId ,
2302- } as any ) . catch ( ( error ) => {
2299+ try {
2300+ await flushGatewayBridgeEventsForRequest ( gatewayRequest . requestId ) ;
2301+ await invoke ( "gateway_chat_complete" , {
2302+ request_id : gatewayRequest . requestId ,
2303+ conversation_id : targetConversationId ,
2304+ worker_id : gatewayWorkerId ,
2305+ } as any ) ;
2306+ } catch ( error ) {
23032307 console . warn ( "gateway_chat_complete failed" , error ) ;
2304- } ) ;
2308+ await invoke ( "gateway_chat_fail" , {
2309+ request_id : gatewayRequest . requestId ,
2310+ conversation_id : targetConversationId ,
2311+ error_code : "desktop_event_flush_failed" ,
2312+ message : asErrorMessage (
2313+ error ,
2314+ "Failed to flush gateway chat events before completing queued request." ,
2315+ ) ,
2316+ terminal : true ,
2317+ worker_id : gatewayWorkerId ,
2318+ } as any ) . catch ( ( failError ) => {
2319+ console . warn ( "gateway_chat_fail failed" , failError ) ;
2320+ } ) ;
2321+ }
23052322 }
23062323 return accepted ;
23072324 } )
@@ -3441,6 +3458,7 @@ export function ChatPage(props: ChatPageProps) {
34413458 ensureGatewayBridgeConversationReadyRef,
34423459 sendActionRef,
34433460 queueGatewayBridgeEventForRequest,
3461+ flushGatewayBridgeEventsForRequest,
34443462 shouldQueueGatewayChatRequest,
34453463 enqueueGatewayChatRequest,
34463464 isConversationRunning,
@@ -3517,12 +3535,54 @@ export function ChatPage(props: ChatPageProps) {
35173535 gatewayBridgeRequest ?. requestId ?? createLocalGatewayChatRunId ( conversationId ) ;
35183536 const gatewayBridgeWorkerId =
35193537 gatewayBridgeRequest ?. workerId ?? ( mirrorsLocalRunToGateway ? "gui-live" : undefined ) ;
3538+ let gatewayBridgeAbortSignal : AbortSignal | null = null ;
3539+ const waitForLocalGatewayMirror = ( sendResult : Promise < void > | void ) => {
3540+ const sendPromise = Promise . resolve ( sendResult ) ;
3541+ const signal = gatewayBridgeAbortSignal ;
3542+ if ( ! signal ) {
3543+ return sendPromise ;
3544+ }
3545+ if ( signal . aborted ) {
3546+ return Promise . resolve ( ) ;
3547+ }
3548+ return new Promise < void > ( ( resolve , reject ) => {
3549+ const handleAbort = ( ) => {
3550+ resolve ( ) ;
3551+ } ;
3552+ signal . addEventListener ( "abort" , handleAbort , { once : true } ) ;
3553+ sendPromise . then (
3554+ ( ) => {
3555+ signal . removeEventListener ( "abort" , handleAbort ) ;
3556+ resolve ( ) ;
3557+ } ,
3558+ ( error ) => {
3559+ signal . removeEventListener ( "abort" , handleAbort ) ;
3560+ reject ( error ) ;
3561+ } ,
3562+ ) ;
3563+ } ) ;
3564+ } ;
3565+ const sendGatewayBridgeEventForRun = mirrorsLocalRunToGateway
3566+ ? (
3567+ requestId : string ,
3568+ event : Record < string , unknown > ,
3569+ options ?: { workerId ?: string } ,
3570+ ) => {
3571+ const sendPromise = waitForLocalGatewayMirror (
3572+ queueGatewayBridgeEventForRequest ( requestId , event , options ) ,
3573+ ) ;
3574+ sendPromise . catch ( ( error ) => {
3575+ console . warn ( "local gateway chat event mirror failed" , error ) ;
3576+ } ) ;
3577+ return sendPromise ;
3578+ }
3579+ : queueGatewayBridgeEventForRequest ;
35203580 const gatewayBridgeEvents = createGatewayBridgeEventController ( {
35213581 conversationId,
35223582 requestId : gatewayBridgeRequestId ,
35233583 workerId : gatewayBridgeWorkerId ,
35243584 enabled : Boolean ( gatewayBridgeRequest ) || hasRemoteGatewayTarget ,
3525- sendEvent : queueGatewayBridgeEventForRequest ,
3585+ sendEvent : sendGatewayBridgeEventForRun ,
35263586 resolveErrorConversationId : ( ) =>
35273587 gatewayBridgeRequest ?. conversationId ?? currentConversationIdRef . current ,
35283588 } ) ;
@@ -3696,6 +3756,7 @@ export function ChatPage(props: ChatPageProps) {
36963756 const conversationThrottleState = getCompactionThrottleState ( conversationId ) ;
36973757 const isConversationVisible = ( ) => currentConversationIdRef . current === conversationId ;
36983758 let requestController = new AbortController ( ) ;
3759+ gatewayBridgeAbortSignal = requestController . signal ;
36993760 const conversationDebugLogger = createStreamDebugLogger ( {
37003761 enabled : effectiveIsAgentDevExecutionMode ,
37013762 conversationId,
@@ -3821,6 +3882,22 @@ export function ChatPage(props: ChatPageProps) {
38213882 queueGatewayConversationActivity ( conversationId , false , conversationCwd ) ;
38223883 }
38233884 }
3885+ function finishAbortedBeforeRuntimeStart ( ) {
3886+ if ( ! requestController . signal . aborted ) {
3887+ return false ;
3888+ }
3889+ gatewayBridgeEvents . queueEvent ( {
3890+ type : "error" ,
3891+ message : "Cancelled" ,
3892+ conversation_id : conversationId ,
3893+ } ) ;
3894+ gatewayBridgeEvents . close ( ) ;
3895+ updateToolStatus ( null , transcriptStore , isConversationVisible ( ) ) ;
3896+ clearAbortSnapshot ( transcriptStore ) ;
3897+ markConversationRunStopped ( ) ;
3898+ requestQueuedChatTurnProcessing ( conversationId ) ;
3899+ return true ;
3900+ }
38243901 let localGatewayRunStarted = false ;
38253902 async function markLocalGatewayRunStarted ( ) {
38263903 if ( ! mirrorsLocalRunToGateway || localGatewayRunStarted ) {
@@ -3836,9 +3913,18 @@ export function ChatPage(props: ChatPageProps) {
38363913 markConversationRunStarted ( ) ;
38373914 if ( mirrorsLocalRunToGateway ) {
38383915 try {
3839- await markLocalGatewayRunStarted ( ) ;
3916+ await waitForLocalGatewayMirror ( markLocalGatewayRunStarted ( ) ) ;
38403917 } catch ( error ) {
3918+ const message = asErrorMessage ( error , "Gateway 本地会话启动同步失败" ) ;
3919+ setConversationErrorState ( message ) ;
3920+ gatewayBridgeEvents . emitError ( message , conversationId ) ;
3921+ gatewayBridgeEvents . close ( ) ;
3922+ markConversationRunStopped ( ) ;
38413923 console . warn ( "gateway_chat_mark_local_started failed" , error ) ;
3924+ return true ;
3925+ }
3926+ if ( finishAbortedBeforeRuntimeStart ( ) ) {
3927+ return true ;
38423928 }
38433929 }
38443930 if ( overrides ?. beforeRuntimeStart ) {
@@ -3852,6 +3938,9 @@ export function ChatPage(props: ChatPageProps) {
38523938 markConversationRunStopped ( ) ;
38533939 return false ;
38543940 }
3941+ if ( finishAbortedBeforeRuntimeStart ( ) ) {
3942+ return true ;
3943+ }
38553944 }
38563945
38573946 // Persist the user turn immediately so WebUI/GUI sidebars can surface the
@@ -3920,7 +4009,22 @@ export function ChatPage(props: ChatPageProps) {
39204009 console . warn ( "gateway stream started before initial user turn was persisted" ) ;
39214010 }
39224011 }
3923- await gatewayBridgeEvents . queueUserMessage ( text , uploadedFiles ) ;
4012+ try {
4013+ await gatewayBridgeEvents . queueUserMessage ( text , uploadedFiles ) ;
4014+ } catch ( error ) {
4015+ if ( ! mirrorsLocalRunToGateway ) {
4016+ throw error ;
4017+ }
4018+ const message = asErrorMessage ( error , "Gateway 用户消息同步失败" ) ;
4019+ setConversationErrorState ( message ) ;
4020+ gatewayBridgeEvents . emitError ( message , conversationId ) ;
4021+ gatewayBridgeEvents . close ( ) ;
4022+ markConversationRunStopped ( ) ;
4023+ return true ;
4024+ }
4025+ if ( finishAbortedBeforeRuntimeStart ( ) ) {
4026+ return true ;
4027+ }
39244028 acknowledgeGatewayRunStarted ( ) ;
39254029 let activeCompactionRollback : {
39264030 state : ConversationViewState ;
@@ -4217,6 +4321,7 @@ export function ChatPage(props: ChatPageProps) {
42174321
42184322 function renewRequestController ( ) {
42194323 requestController = new AbortController ( ) ;
4324+ gatewayBridgeAbortSignal = requestController . signal ;
42204325 setConversationAbortController ( conversationId , requestController ) ;
42214326 return requestController ;
42224327 }
@@ -4489,6 +4594,9 @@ export function ChatPage(props: ChatPageProps) {
44894594 hookWarning : null ,
44904595 } ) ) ;
44914596 }
4597+ if ( finishAbortedBeforeRuntimeStart ( ) ) {
4598+ return true ;
4599+ }
44924600
44934601 try {
44944602 if ( effectiveIsAgentMode ) {
0 commit comments