@@ -966,6 +966,290 @@ describe("AgentSession startup auto-retry recovery", () => {
966966 session . dispose ( ) ;
967967 } ) ;
968968
969+ test ( "compaction retry failure preserves the adjusted 1M-context retry request" , async ( ) => {
970+ const workspaceId = "startup-retry-compaction-adjusted-request" ;
971+ const { session, cleanup } = await createSessionBundle ( workspaceId ) ;
972+ cleanups . push ( cleanup ) ;
973+
974+ const baseOptions : SendMessageOptions = {
975+ model : "anthropic:claude-sonnet-4-5" ,
976+ agentId : "compact" ,
977+ } ;
978+ const retriedOptions : SendMessageOptions = {
979+ ...baseOptions ,
980+ providerOptions : {
981+ anthropic : {
982+ use1MContext : true ,
983+ use1MContextModels : [ baseOptions . model ] ,
984+ } ,
985+ } ,
986+ } ;
987+
988+ const privateSession = session as unknown as {
989+ maybeRetryCompactionOnContextExceeded : ( data : {
990+ messageId : string ;
991+ errorType ?: string ;
992+ } ) => Promise < boolean > ;
993+ lastAutoRetryRequest ?: StartupRetrySendOptions ;
994+ activeCompactionRequest ?: {
995+ id : string ;
996+ modelString : string ;
997+ options ?: SendMessageOptions ;
998+ source ?: "idle-compaction" | "auto-compaction" ;
999+ } ;
1000+ activeStreamContext ?: {
1001+ modelString : string ;
1002+ options ?: SendMessageOptions ;
1003+ agentInitiated ?: boolean ;
1004+ openaiTruncationModeOverride ?: "auto" | "disabled" ;
1005+ providersConfig : unknown ;
1006+ } ;
1007+ supports1MContextRetry : ( modelString : string ) => boolean ;
1008+ is1MContextEnabledForModel : (
1009+ modelString : string ,
1010+ options ?: SendMessageOptions ,
1011+ providersConfig ?: unknown
1012+ ) => boolean ;
1013+ withAnthropic1MContext : (
1014+ modelString : string ,
1015+ options ?: SendMessageOptions
1016+ ) => SendMessageOptions | null ;
1017+ finalizeCompactionRetry : ( messageId : string ) => Promise < void > ;
1018+ streamWithHistory : (
1019+ modelString : string ,
1020+ options ?: SendMessageOptions ,
1021+ openaiTruncationModeOverride ?: "auto" | "disabled" ,
1022+ disablePostCompactionAttachments ?: boolean ,
1023+ agentInitiated ?: boolean
1024+ ) => Promise <
1025+ | { success : true ; data : undefined }
1026+ | { success : false ; error : { type : "runtime_start_failed" ; message : string } }
1027+ > ;
1028+ } ;
1029+
1030+ privateSession . lastAutoRetryRequest = {
1031+ model : "openai:gpt-4o-mini" ,
1032+ agentId : "compact" ,
1033+ agentInitiated : true ,
1034+ } ;
1035+ privateSession . activeCompactionRequest = {
1036+ id : "compaction-request-1" ,
1037+ modelString : baseOptions . model ,
1038+ options : baseOptions ,
1039+ source : "auto-compaction" ,
1040+ } ;
1041+ privateSession . activeStreamContext = {
1042+ modelString : baseOptions . model ,
1043+ options : baseOptions ,
1044+ agentInitiated : true ,
1045+ providersConfig : null ,
1046+ } ;
1047+ privateSession . supports1MContextRetry = mock ( ( ) => true ) ;
1048+ privateSession . is1MContextEnabledForModel = mock ( ( ) => false ) ;
1049+ privateSession . withAnthropic1MContext = mock ( ( ) => retriedOptions ) ;
1050+ privateSession . finalizeCompactionRetry = mock ( ( ) => Promise . resolve ( ) ) ;
1051+ const streamWithHistoryMock = mock ( ( ) =>
1052+ Promise . resolve ( {
1053+ success : false as const ,
1054+ error : {
1055+ type : "runtime_start_failed" as const ,
1056+ message : "retry startup failed" ,
1057+ } ,
1058+ } )
1059+ ) ;
1060+ privateSession . streamWithHistory = streamWithHistoryMock ;
1061+
1062+ const retried = await privateSession . maybeRetryCompactionOnContextExceeded ( {
1063+ messageId : "assistant-retry-failure" ,
1064+ errorType : "context_exceeded" ,
1065+ } ) ;
1066+
1067+ expect ( retried ) . toBe ( false ) ;
1068+ expect ( streamWithHistoryMock ) . toHaveBeenCalledTimes ( 1 ) ;
1069+ expect ( privateSession . lastAutoRetryRequest ?. model ) . toBe ( baseOptions . model ) ;
1070+ expect ( privateSession . lastAutoRetryRequest ?. agentId ) . toBe ( "compact" ) ;
1071+ expect ( privateSession . lastAutoRetryRequest ?. providerOptions ?. anthropic ?. use1MContext ) . toBe (
1072+ true
1073+ ) ;
1074+ expect (
1075+ privateSession . lastAutoRetryRequest ?. providerOptions ?. anthropic ?. use1MContextModels
1076+ ) . toEqual ( [ baseOptions . model ] ) ;
1077+ expect ( privateSession . lastAutoRetryRequest ?. agentInitiated ) . toBe ( true ) ;
1078+
1079+ session . dispose ( ) ;
1080+ } ) ;
1081+
1082+ test ( "exec-subagent hard-restart retry failure preserves the rebuilt continuation request" , async ( ) => {
1083+ const workspaceId = "startup-retry-hard-restart-request" ;
1084+ const { historyService, config, cleanup } = await createTestHistoryService ( ) ;
1085+ cleanups . push ( cleanup ) ;
1086+
1087+ const appendSnapshotResult = await historyService . appendToHistory (
1088+ workspaceId ,
1089+ createMuxMessage ( "snapshot-1" , "user" , "<snapshot>" , {
1090+ timestamp : Date . now ( ) ,
1091+ synthetic : true ,
1092+ fileAtMentionSnapshot : [ "token" ] ,
1093+ } )
1094+ ) ;
1095+ expect ( appendSnapshotResult . success ) . toBe ( true ) ;
1096+
1097+ const appendPromptResult = await historyService . appendToHistory (
1098+ workspaceId ,
1099+ createMuxMessage ( "user-1" , "user" , "Do the thing" , {
1100+ timestamp : Date . now ( ) ,
1101+ } )
1102+ ) ;
1103+ expect ( appendPromptResult . success ) . toBe ( true ) ;
1104+
1105+ const parentWorkspaceId = "startup-retry-hard-restart-parent" ;
1106+ const childWorkspaceMetadata : WorkspaceMetadata = {
1107+ id : workspaceId ,
1108+ name : "child" ,
1109+ projectName : "project" ,
1110+ projectPath : "/tmp/project" ,
1111+ runtimeConfig : DEFAULT_RUNTIME_CONFIG ,
1112+ aiSettingsByAgent : {
1113+ [ WORKSPACE_DEFAULTS . agentId ] : {
1114+ model : "openai:gpt-4o" ,
1115+ thinkingLevel : "medium" ,
1116+ } ,
1117+ } ,
1118+ parentWorkspaceId,
1119+ agentId : "exec" ,
1120+ } ;
1121+ const parentWorkspaceMetadata : WorkspaceMetadata = {
1122+ ...childWorkspaceMetadata ,
1123+ id : parentWorkspaceId ,
1124+ name : "parent" ,
1125+ parentWorkspaceId : undefined ,
1126+ } ;
1127+
1128+ const aiService : AIService = {
1129+ on ( _eventName : string | symbol , _listener : ( ...args : unknown [ ] ) => void ) {
1130+ return this ;
1131+ } ,
1132+ off ( _eventName : string | symbol , _listener : ( ...args : unknown [ ] ) => void ) {
1133+ return this ;
1134+ } ,
1135+ isStreaming : mock ( ( ) => false ) ,
1136+ stopStream : mock ( ( ) => Promise . resolve ( Ok ( undefined ) ) ) ,
1137+ streamMessage : mock ( ( ) => Promise . resolve ( Ok ( undefined ) ) ) ,
1138+ getWorkspaceMetadata : mock ( ( id : string ) => {
1139+ if ( id === workspaceId ) {
1140+ return Promise . resolve ( Ok ( childWorkspaceMetadata ) ) ;
1141+ }
1142+
1143+ if ( id === parentWorkspaceId ) {
1144+ return Promise . resolve ( Ok ( parentWorkspaceMetadata ) ) ;
1145+ }
1146+
1147+ return Promise . resolve ( { success : false as const , error : "unknown workspace" } ) ;
1148+ } ) ,
1149+ } as unknown as AIService ;
1150+
1151+ const initStateManager : InitStateManager = {
1152+ on ( ) {
1153+ return this ;
1154+ } ,
1155+ off ( ) {
1156+ return this ;
1157+ } ,
1158+ } as unknown as InitStateManager ;
1159+
1160+ const backgroundProcessManager : BackgroundProcessManager = {
1161+ cleanup : mock ( ( ) => Promise . resolve ( ) ) ,
1162+ setMessageQueued : mock ( ( ) => undefined ) ,
1163+ } as unknown as BackgroundProcessManager ;
1164+
1165+ const session = new AgentSession ( {
1166+ workspaceId,
1167+ config,
1168+ historyService,
1169+ aiService,
1170+ initStateManager,
1171+ backgroundProcessManager,
1172+ } ) ;
1173+
1174+ const baseOptions : SendMessageOptions = {
1175+ model : "openai:gpt-4o" ,
1176+ agentId : "exec" ,
1177+ additionalSystemInstructions : "Follow the existing plan." ,
1178+ experiments : {
1179+ execSubagentHardRestart : true ,
1180+ } ,
1181+ } ;
1182+
1183+ const privateSession = session as unknown as {
1184+ maybeHardRestartExecSubagentOnContextExceeded : ( data : {
1185+ messageId : string ;
1186+ errorType ?: string ;
1187+ } ) => Promise < boolean > ;
1188+ lastAutoRetryRequest ?: StartupRetrySendOptions ;
1189+ activeStreamContext ?: {
1190+ modelString : string ;
1191+ options ?: SendMessageOptions ;
1192+ agentInitiated ?: boolean ;
1193+ openaiTruncationModeOverride ?: "auto" | "disabled" ;
1194+ providersConfig : unknown ;
1195+ } ;
1196+ activeStreamUserMessageId ?: string ;
1197+ streamWithHistory : (
1198+ modelString : string ,
1199+ options ?: SendMessageOptions ,
1200+ openaiTruncationModeOverride ?: "auto" | "disabled" ,
1201+ disablePostCompactionAttachments ?: boolean ,
1202+ agentInitiated ?: boolean
1203+ ) => Promise <
1204+ | { success : true ; data : undefined }
1205+ | { success : false ; error : { type : "runtime_start_failed" ; message : string } }
1206+ > ;
1207+ } ;
1208+
1209+ privateSession . lastAutoRetryRequest = {
1210+ model : "openai:gpt-4o-mini" ,
1211+ agentId : "exec" ,
1212+ agentInitiated : true ,
1213+ } ;
1214+ privateSession . activeStreamContext = {
1215+ modelString : baseOptions . model ,
1216+ options : baseOptions ,
1217+ agentInitiated : true ,
1218+ providersConfig : null ,
1219+ } ;
1220+ privateSession . activeStreamUserMessageId = "user-1" ;
1221+ const streamWithHistoryMock = mock ( ( ) =>
1222+ Promise . resolve ( {
1223+ success : false as const ,
1224+ error : {
1225+ type : "runtime_start_failed" as const ,
1226+ message : "hard restart startup failed" ,
1227+ } ,
1228+ } )
1229+ ) ;
1230+ privateSession . streamWithHistory = streamWithHistoryMock ;
1231+
1232+ const retried = await privateSession . maybeHardRestartExecSubagentOnContextExceeded ( {
1233+ messageId : "assistant-hard-restart-failure" ,
1234+ errorType : "context_exceeded" ,
1235+ } ) ;
1236+
1237+ expect ( retried ) . toBe ( false ) ;
1238+ expect ( streamWithHistoryMock ) . toHaveBeenCalledTimes ( 1 ) ;
1239+ expect ( privateSession . lastAutoRetryRequest ?. model ) . toBe ( baseOptions . model ) ;
1240+ expect ( privateSession . lastAutoRetryRequest ?. agentId ) . toBe ( "exec" ) ;
1241+ expect ( privateSession . lastAutoRetryRequest ?. experiments ?. execSubagentHardRestart ) . toBe ( true ) ;
1242+ expect ( privateSession . lastAutoRetryRequest ?. agentInitiated ) . toBe ( true ) ;
1243+ expect ( privateSession . lastAutoRetryRequest ?. additionalSystemInstructions ) . toContain (
1244+ "Context limit reached"
1245+ ) ;
1246+ expect ( privateSession . lastAutoRetryRequest ?. additionalSystemInstructions ) . toContain (
1247+ "Follow the existing plan."
1248+ ) ;
1249+
1250+ session . dispose ( ) ;
1251+ } ) ;
1252+
9691253 test ( "persists startup abandon marker for pre-stream user aborts" , async ( ) => {
9701254 const workspaceId = "startup-retry-pre-stream-abort" ;
9711255 const { historyService, config, cleanup } = await createTestHistoryService ( ) ;
0 commit comments