@@ -154,6 +154,7 @@ import { ADMIN_SCOPE } from "../method-scopes.js";
154154import { chatAbortMarkerTimestampMs , type ChatRunTiming } from "../server-chat-state.js" ;
155155import { getMaxChatHistoryMessagesBytes , MAX_PAYLOAD_BYTES } from "../server-constants.js" ;
156156import { resolveSessionHistoryTailReadOptions } from "../session-history-state.js" ;
157+ import { persistGatewaySessionLifecycleEvent } from "../session-lifecycle-state.js" ;
157158import { readSessionTranscriptIndex } from "../session-transcript-index.fs.js" ;
158159import {
159160 capArrayByJsonBytes ,
@@ -3725,6 +3726,14 @@ export const chatHandlers: GatewayRequestHandlers = {
37253726 const deliveredReplies : Array < { payload : ReplyPayload ; kind : "block" | "final" } > = [ ] ;
37263727 let appendedWebchatAgentMedia = false ;
37273728 let agentRunStarted = false ;
3729+ let pendingDispatchLifecycleError :
3730+ | {
3731+ endedAt : number ;
3732+ error : string ;
3733+ sessionId : string ;
3734+ startedAt : number ;
3735+ }
3736+ | undefined ;
37283737 const userTurnRecorder : UserTurnTranscriptRecorder = createUserTurnTranscriptRecorder ( {
37293738 input : baseUserTurnInput ,
37303739 resolveInput : ( ) => userTurnInputPromise ,
@@ -4934,6 +4943,7 @@ export const chatHandlers: GatewayRequestHandlers = {
49344943 ) ;
49354944 } )
49364945 . catch ( async ( err : unknown ) => {
4946+ const errorMessage = String ( err ) ;
49374947 const emitAfterError =
49384948 userTurnRecorder . hasPersisted ( ) || userTurnRecorder . isBlocked ( )
49394949 ? Promise . resolve ( )
@@ -4943,7 +4953,19 @@ export const chatHandlers: GatewayRequestHandlers = {
49434953 `webchat user transcript update failed after error: ${ formatForLog ( transcriptErr ) } ` ,
49444954 ) ;
49454955 } ) ;
4946- const error = errorShape ( ErrorCodes . UNAVAILABLE , String ( err ) ) ;
4956+ if (
4957+ ! agentRunStarted &&
4958+ ! activeRunAbort . controller . signal . aborted &&
4959+ ! context . chatAbortedRuns . has ( clientRunId )
4960+ ) {
4961+ pendingDispatchLifecycleError = {
4962+ endedAt : Date . now ( ) ,
4963+ error : errorMessage ,
4964+ sessionId : activeRunAbort . entry ?. sessionId ?? backingSessionId ?? clientRunId ,
4965+ startedAt : activeRunAbort . entry ?. startedAtMs ?? now ,
4966+ } ;
4967+ }
4968+ const error = errorShape ( ErrorCodes . UNAVAILABLE , errorMessage ) ;
49474969 setGatewayDedupeEntry ( {
49484970 dedupe : context . dedupe ,
49494971 key : `chat:${ clientRunId } ` ,
@@ -4953,7 +4975,7 @@ export const chatHandlers: GatewayRequestHandlers = {
49534975 payload : {
49544976 runId : clientRunId ,
49554977 status : "error" as const ,
4956- summary : String ( err ) ,
4978+ summary : errorMessage ,
49574979 } ,
49584980 error,
49594981 } ,
@@ -4963,14 +4985,57 @@ export const chatHandlers: GatewayRequestHandlers = {
49634985 runId : clientRunId ,
49644986 sessionKey,
49654987 agentId,
4966- errorMessage : String ( err ) ,
4988+ errorMessage,
49674989 } ) ;
49684990 } )
4969- . finally ( ( ) => {
4991+ . finally ( async ( ) => {
49704992 activeRunAbort . cleanup ( ) ;
49714993 clearAgentRunContext ( clientRunId , lifecycleGeneration ) ;
49724994 clearActiveChatSendDedupeRun ( context . dedupe , activeChatSendDedupeKey , clientRunId ) ;
49734995 context . removeChatRun ( clientRunId , clientRunId , sessionKey ) ;
4996+ if ( ! pendingDispatchLifecycleError ) {
4997+ return ;
4998+ }
4999+ const hasActiveRun = hasTrackedActiveSessionRun ( {
5000+ context,
5001+ requestedKey : rawSessionKey ,
5002+ canonicalKey : sessionKey ,
5003+ ...( sessionKey === "global" && agentId ? { agentId } : { } ) ,
5004+ defaultAgentId : resolveDefaultAgentId ( cfg ) ,
5005+ } ) ;
5006+ if ( hasActiveRun ) {
5007+ return ;
5008+ }
5009+ const persisted = await persistGatewaySessionLifecycleEvent ( {
5010+ sessionKey,
5011+ ...( sessionKey === "global" && agentId ? { agentId } : { } ) ,
5012+ event : {
5013+ runId : clientRunId ,
5014+ sessionId : pendingDispatchLifecycleError . sessionId ,
5015+ lifecycleGeneration,
5016+ ts : pendingDispatchLifecycleError . endedAt ,
5017+ data : {
5018+ phase : "error" ,
5019+ startedAt : pendingDispatchLifecycleError . startedAt ,
5020+ endedAt : pendingDispatchLifecycleError . endedAt ,
5021+ error : pendingDispatchLifecycleError . error ,
5022+ } ,
5023+ } ,
5024+ } )
5025+ . then ( ( ) => true )
5026+ . catch ( ( persistErr ) => {
5027+ context . logGateway . warn (
5028+ `webchat session lifecycle persist failed after error: ${ formatForLog ( persistErr ) } ` ,
5029+ ) ;
5030+ return false ;
5031+ } ) ;
5032+ if ( persisted ) {
5033+ emitSessionsChanged ( context , {
5034+ sessionKey,
5035+ ...( agentId ? { agentId } : { } ) ,
5036+ reason : "chat.dispatch-error" ,
5037+ } ) ;
5038+ }
49745039 } ) ;
49755040 } catch ( err ) {
49765041 activeRunAbort . cleanup ( { force : true } ) ;
0 commit comments