@@ -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 ,
@@ -3746,6 +3747,14 @@ export const chatHandlers: GatewayRequestHandlers = {
37463747 const deliveredReplies : Array < { payload : ReplyPayload ; kind : "block" | "final" } > = [ ] ;
37473748 let appendedWebchatAgentMedia = false ;
37483749 let agentRunStarted = false ;
3750+ let pendingDispatchLifecycleError :
3751+ | {
3752+ endedAt : number ;
3753+ error : string ;
3754+ sessionId : string ;
3755+ startedAt : number ;
3756+ }
3757+ | undefined ;
37493758 const userTurnRecorder : UserTurnTranscriptRecorder = createUserTurnTranscriptRecorder ( {
37503759 input : baseUserTurnInput ,
37513760 resolveInput : ( ) => userTurnInputPromise ,
@@ -4957,6 +4966,7 @@ export const chatHandlers: GatewayRequestHandlers = {
49574966 ) ;
49584967 } )
49594968 . catch ( async ( err : unknown ) => {
4969+ const errorMessage = String ( err ) ;
49604970 const emitAfterError =
49614971 userTurnRecorder . hasPersisted ( ) || userTurnRecorder . isBlocked ( )
49624972 ? Promise . resolve ( )
@@ -4966,7 +4976,19 @@ export const chatHandlers: GatewayRequestHandlers = {
49664976 `webchat user transcript update failed after error: ${ formatForLog ( transcriptErr ) } ` ,
49674977 ) ;
49684978 } ) ;
4969- const error = errorShape ( ErrorCodes . UNAVAILABLE , String ( err ) ) ;
4979+ if (
4980+ ! agentRunStarted &&
4981+ ! activeRunAbort . controller . signal . aborted &&
4982+ ! context . chatAbortedRuns . has ( clientRunId )
4983+ ) {
4984+ pendingDispatchLifecycleError = {
4985+ endedAt : Date . now ( ) ,
4986+ error : errorMessage ,
4987+ sessionId : activeRunAbort . entry ?. sessionId ?? backingSessionId ?? clientRunId ,
4988+ startedAt : activeRunAbort . entry ?. startedAtMs ?? now ,
4989+ } ;
4990+ }
4991+ const error = errorShape ( ErrorCodes . UNAVAILABLE , errorMessage ) ;
49704992 setGatewayDedupeEntry ( {
49714993 dedupe : context . dedupe ,
49724994 key : `chat:${ clientRunId } ` ,
@@ -4976,7 +4998,7 @@ export const chatHandlers: GatewayRequestHandlers = {
49764998 payload : {
49774999 runId : clientRunId ,
49785000 status : "error" as const ,
4979- summary : String ( err ) ,
5001+ summary : errorMessage ,
49805002 } ,
49815003 error,
49825004 } ,
@@ -4986,14 +5008,61 @@ export const chatHandlers: GatewayRequestHandlers = {
49865008 runId : clientRunId ,
49875009 sessionKey,
49885010 agentId,
4989- errorMessage : String ( err ) ,
5011+ errorMessage,
49905012 } ) ;
49915013 } )
49925014 . finally ( ( ) => {
49935015 activeRunAbort . cleanup ( ) ;
49945016 clearAgentRunContext ( clientRunId , lifecycleGeneration ) ;
49955017 clearActiveChatSendDedupeRun ( context . dedupe , activeChatSendDedupeKey , clientRunId ) ;
49965018 context . removeChatRun ( clientRunId , clientRunId , sessionKey ) ;
5019+ if ( ! pendingDispatchLifecycleError ) {
5020+ return ;
5021+ }
5022+ const persistDispatchLifecycleError = async ( ) => {
5023+ const dispatchError = pendingDispatchLifecycleError ;
5024+ if ( ! dispatchError ) {
5025+ return ;
5026+ }
5027+ const hasActiveRun = hasTrackedActiveSessionRun ( {
5028+ context,
5029+ requestedKey : rawSessionKey ,
5030+ canonicalKey : sessionKey ,
5031+ ...( sessionKey === "global" && agentId ? { agentId } : { } ) ,
5032+ defaultAgentId : resolveDefaultAgentId ( cfg ) ,
5033+ } ) ;
5034+ if ( hasActiveRun ) {
5035+ return ;
5036+ }
5037+ try {
5038+ await persistGatewaySessionLifecycleEvent ( {
5039+ sessionKey,
5040+ ...( sessionKey === "global" && agentId ? { agentId } : { } ) ,
5041+ event : {
5042+ runId : clientRunId ,
5043+ sessionId : dispatchError . sessionId ,
5044+ lifecycleGeneration,
5045+ ts : dispatchError . endedAt ,
5046+ data : {
5047+ phase : "error" ,
5048+ startedAt : dispatchError . startedAt ,
5049+ endedAt : dispatchError . endedAt ,
5050+ error : dispatchError . error ,
5051+ } ,
5052+ } ,
5053+ } ) ;
5054+ emitSessionsChanged ( context , {
5055+ sessionKey,
5056+ ...( agentId ? { agentId } : { } ) ,
5057+ reason : "chat.dispatch-error" ,
5058+ } ) ;
5059+ } catch ( persistErr : unknown ) {
5060+ context . logGateway . warn (
5061+ `webchat session lifecycle persist failed after error: ${ formatForLog ( persistErr ) } ` ,
5062+ ) ;
5063+ }
5064+ } ;
5065+ void persistDispatchLifecycleError ( ) ;
49975066 } ) ;
49985067 } catch ( err ) {
49995068 activeRunAbort . cleanup ( { force : true } ) ;
0 commit comments