@@ -29,6 +29,7 @@ import {
2929 parseExecApprovalRequested ,
3030 parseExecApprovalResolved ,
3131 parsePluginApprovalRequested ,
32+ pruneExecApprovalQueue ,
3233 removeExecApproval ,
3334} from "./controllers/exec-approval.ts" ;
3435import { loadHealthState } from "./controllers/health.ts" ;
@@ -98,6 +99,11 @@ type SessionDefaultsSnapshot = {
9899
99100type GatewayHostWithShutdownMessage = GatewayHost & {
100101 pendingShutdownMessage ?: string | null ;
102+ resumeChatQueueAfterReconnect ?: boolean ;
103+ } ;
104+
105+ type ConnectGatewayOptions = {
106+ reason ?: "initial" | "seq-gap" ;
101107} ;
102108
103109export function resolveControlUiClientVersion ( params : {
@@ -179,14 +185,27 @@ function applySessionDefaults(host: GatewayHost, defaults?: SessionDefaultsSnaps
179185 }
180186}
181187
182- export function connectGateway ( host : GatewayHost ) {
188+ export function connectGateway ( host : GatewayHost , options ?: ConnectGatewayOptions ) {
183189 const shutdownHost = host as GatewayHostWithShutdownMessage ;
190+ const reconnectReason = options ?. reason ?? "initial" ;
184191 shutdownHost . pendingShutdownMessage = null ;
192+ shutdownHost . resumeChatQueueAfterReconnect = false ;
185193 host . lastError = null ;
186194 host . lastErrorCode = null ;
187195 host . hello = null ;
188196 host . connected = false ;
189- host . execApprovalQueue = [ ] ;
197+ if ( reconnectReason === "seq-gap" ) {
198+ // A seq gap means the socket stayed on the same gateway; preserve prompts
199+ // that only arrived as ephemeral events and clear stale run-scoped indicators.
200+ host . execApprovalQueue = pruneExecApprovalQueue ( host . execApprovalQueue ) ;
201+ clearPendingQueueItemsForRun (
202+ host as unknown as Parameters < typeof clearPendingQueueItemsForRun > [ 0 ] ,
203+ host . chatRunId ?? undefined ,
204+ ) ;
205+ shutdownHost . resumeChatQueueAfterReconnect = true ;
206+ } else {
207+ host . execApprovalQueue = [ ] ;
208+ }
190209 host . execApprovalError = null ;
191210
192211 const previousClient = host . client ;
@@ -218,6 +237,14 @@ export function connectGateway(host: GatewayHost) {
218237 ( host as unknown as { chatStream : string | null } ) . chatStream = null ;
219238 ( host as unknown as { chatStreamStartedAt : number | null } ) . chatStreamStartedAt = null ;
220239 resetToolStream ( host as unknown as Parameters < typeof resetToolStream > [ 0 ] ) ;
240+ if ( shutdownHost . resumeChatQueueAfterReconnect ) {
241+ // The interrupted run will never emit its terminal event now that the
242+ // old client is gone, so resume any deferred commands after hello.
243+ shutdownHost . resumeChatQueueAfterReconnect = false ;
244+ void flushChatQueueForEvent (
245+ host as unknown as Parameters < typeof flushChatQueueForEvent > [ 0 ] ,
246+ ) ;
247+ }
221248 void subscribeSessions ( host as unknown as OpenClawApp ) ;
222249 void loadAssistantIdentity ( host as unknown as OpenClawApp ) ;
223250 void loadAgents ( host as unknown as OpenClawApp ) ;
@@ -266,7 +293,7 @@ export function connectGateway(host: GatewayHost) {
266293 }
267294 host . lastError = `event gap detected (expected seq ${ expected } , got ${ received } ); reconnecting` ;
268295 host . lastErrorCode = null ;
269- connectGateway ( host ) ;
296+ connectGateway ( host , { reason : "seq-gap" } ) ;
270297 } ,
271298 } ) ;
272299 host . client = client ;
0 commit comments