@@ -8,7 +8,7 @@ import type { HistoryService } from "./historyService";
88import type { Config } from "@/node/config" ;
99import type { InitStateManager } from "./initStateManager" ;
1010import type { WorkspaceChatMessage , SendMessageOptions } from "@/common/orpc/types" ;
11- import { createMuxMessage } from "@/common/types/message" ;
11+ import { createMuxMessage , type StartupRetrySendOptions } from "@/common/types/message" ;
1212import { DEFAULT_RUNTIME_CONFIG } from "@/common/constants/workspace" ;
1313import type { WorkspaceMetadata } from "@/common/types/workspace" ;
1414import { Ok } from "@/common/types/result" ;
@@ -142,8 +142,11 @@ describe("AgentSession startup auto-retry recovery", () => {
142142 const scheduledEvent = events . find ( ( event ) => event . type === "auto-retry-scheduled" ) ;
143143 expect ( scheduledEvent ) . toBeDefined ( ) ;
144144
145- const retryOptions = ( session as unknown as { lastAutoRetryOptions ?: SendMessageOptions } )
146- . lastAutoRetryOptions ;
145+ const retryOptions = (
146+ session as unknown as {
147+ lastAutoRetryRequest ?: StartupRetrySendOptions ;
148+ }
149+ ) . lastAutoRetryRequest ;
147150 expect ( retryOptions ) . toBeDefined ( ) ;
148151 if ( ! retryOptions ) {
149152 throw new Error ( "Expected startup auto-retry options to be captured" ) ;
@@ -454,8 +457,11 @@ describe("AgentSession startup auto-retry recovery", () => {
454457 ) . startupAutoRetryCheckPromise ;
455458 await startupCheckPromise ;
456459
457- const retryOptions = ( session as unknown as { lastAutoRetryOptions ?: SendMessageOptions } )
458- . lastAutoRetryOptions ;
460+ const retryOptions = (
461+ session as unknown as {
462+ lastAutoRetryRequest ?: StartupRetrySendOptions ;
463+ }
464+ ) . lastAutoRetryRequest ;
459465 expect ( retryOptions ) . toBeDefined ( ) ;
460466 if ( ! retryOptions ) {
461467 throw new Error ( "Expected startup retry options" ) ;
@@ -714,7 +720,7 @@ describe("AgentSession startup auto-retry recovery", () => {
714720 persistStartupAutoRetryAbandon : ( reason : string , userMessageId ?: string ) => Promise < void > ;
715721 retryActiveStream : ( ) => Promise < void > ;
716722 getAutoRetryPreferencePath : ( ) => string ;
717- lastAutoRetryOptions ?: SendMessageOptions ;
723+ lastAutoRetryRequest ?: StartupRetrySendOptions ;
718724 resumeStream : (
719725 options : SendMessageOptions
720726 ) => Promise < { success : true ; data : { started : boolean } } > ;
@@ -726,7 +732,7 @@ describe("AgentSession startup auto-retry recovery", () => {
726732 const preferencePath = privateSession . getAutoRetryPreferencePath ( ) ;
727733 expect ( await Bun . file ( preferencePath ) . exists ( ) ) . toBe ( true ) ;
728734
729- privateSession . lastAutoRetryOptions = {
735+ privateSession . lastAutoRetryRequest = {
730736 model : "anthropic:claude-sonnet-4-5" ,
731737 agentId : "exec" ,
732738 } ;
@@ -752,13 +758,13 @@ describe("AgentSession startup auto-retry recovery", () => {
752758
753759 const privateSession = session as unknown as {
754760 retryActiveStream : ( ) => Promise < void > ;
755- lastAutoRetryOptions ?: SendMessageOptions ;
761+ lastAutoRetryRequest ?: StartupRetrySendOptions ;
756762 resumeStream : (
757763 options : SendMessageOptions
758764 ) => Promise < { success : true ; data : { started : boolean } } > ;
759765 } ;
760766
761- privateSession . lastAutoRetryOptions = {
767+ privateSession . lastAutoRetryRequest = {
762768 model : "anthropic:claude-sonnet-4-5" ,
763769 agentId : "exec" ,
764770 } ;
@@ -789,7 +795,7 @@ describe("AgentSession startup auto-retry recovery", () => {
789795
790796 const privateSession = session as unknown as {
791797 retryActiveStream : ( ) => Promise < void > ;
792- lastAutoRetryOptions ?: SendMessageOptions ;
798+ lastAutoRetryRequest ?: StartupRetrySendOptions ;
793799 activeStreamFailureHandled : boolean ;
794800 resumeStream : (
795801 options : SendMessageOptions
@@ -799,7 +805,7 @@ describe("AgentSession startup auto-retry recovery", () => {
799805 > ;
800806 } ;
801807
802- privateSession . lastAutoRetryOptions = {
808+ privateSession . lastAutoRetryRequest = {
803809 model : "anthropic:claude-sonnet-4-5" ,
804810 agentId : "exec" ,
805811 } ;
@@ -834,7 +840,7 @@ describe("AgentSession startup auto-retry recovery", () => {
834840
835841 const privateSession = session as unknown as {
836842 retryActiveStream : ( ) => Promise < void > ;
837- lastAutoRetryOptions ?: SendMessageOptions ;
843+ lastAutoRetryRequest ?: StartupRetrySendOptions ;
838844 activeStreamFailureHandled : boolean ;
839845 resumeStream : (
840846 options : SendMessageOptions
@@ -844,7 +850,7 @@ describe("AgentSession startup auto-retry recovery", () => {
844850 > ;
845851 } ;
846852
847- privateSession . lastAutoRetryOptions = {
853+ privateSession . lastAutoRetryRequest = {
848854 model : "anthropic:claude-sonnet-4-5" ,
849855 agentId : "exec" ,
850856 } ;
@@ -872,6 +878,94 @@ describe("AgentSession startup auto-retry recovery", () => {
872878 session . dispose ( ) ;
873879 } ) ;
874880
881+ test ( "retryActiveStream resumes the reconstructed follow-up after compaction handoff send fails" , async ( ) => {
882+ const workspaceId = "startup-retry-follow-up-handoff" ;
883+ const { session, historyService, cleanup } = await createSessionBundle ( workspaceId ) ;
884+ cleanups . push ( cleanup ) ;
885+
886+ const appendResult = await historyService . appendToHistory (
887+ workspaceId ,
888+ createMuxMessage ( "summary-follow-up" , "assistant" , "Compaction summary" , {
889+ muxMetadata : {
890+ type : "compaction-summary" ,
891+ pendingFollowUp : {
892+ text : "resume the original work" ,
893+ model : "openai:gpt-4o" ,
894+ agentId : "exec" ,
895+ thinkingLevel : "high" ,
896+ } ,
897+ } ,
898+ } )
899+ ) ;
900+ expect ( appendResult . success ) . toBe ( true ) ;
901+
902+ const privateSession = session as unknown as {
903+ dispatchPendingFollowUp : ( ) => Promise < boolean > ;
904+ retryActiveStream : ( ) => Promise < void > ;
905+ lastAutoRetryRequest ?: StartupRetrySendOptions ;
906+ sendMessage : (
907+ message : string ,
908+ options ?: SendMessageOptions ,
909+ internal ?: { synthetic ?: boolean }
910+ ) => Promise <
911+ { success : true } | { success : false ; error : { type : string ; message ?: string } }
912+ > ;
913+ resumeStream : (
914+ options : SendMessageOptions ,
915+ internal ?: { agentInitiated ?: boolean }
916+ ) => Promise < { success : true ; data : { started : boolean } } > ;
917+ } ;
918+
919+ privateSession . lastAutoRetryRequest = {
920+ model : "anthropic:claude-sonnet-4-5" ,
921+ agentId : "compact" ,
922+ toolPolicy : [ { regex_match : ".*" , action : "disable" } ] ,
923+ agentInitiated : true ,
924+ } ;
925+ privateSession . sendMessage = mock ( ( ) =>
926+ Promise . resolve ( {
927+ success : false as const ,
928+ error : { type : "runtime_start_failed" , message : "startup failed" } ,
929+ } )
930+ ) ;
931+
932+ let dispatchError : unknown ;
933+ try {
934+ await privateSession . dispatchPendingFollowUp ( ) ;
935+ } catch ( error ) {
936+ dispatchError = error ;
937+ }
938+ expect ( dispatchError ) . toBeInstanceOf ( Error ) ;
939+ expect ( ( dispatchError as Error ) . message ) . toContain ( "Failed to dispatch pending follow-up" ) ;
940+
941+ const resumeStreamMock = mock (
942+ ( _options : SendMessageOptions , _internal ?: { agentInitiated ?: boolean } ) =>
943+ Promise . resolve ( { success : true as const , data : { started : true } } )
944+ ) ;
945+ privateSession . resumeStream = resumeStreamMock ;
946+
947+ await privateSession . retryActiveStream ( ) ;
948+
949+ expect ( resumeStreamMock ) . toHaveBeenCalledTimes ( 1 ) ;
950+ const firstCall = resumeStreamMock . mock . calls [ 0 ] ;
951+ expect ( firstCall ) . toBeDefined ( ) ;
952+ const [ optionsArg , internalArg ] = firstCall as unknown as [
953+ SendMessageOptions ,
954+ { agentInitiated ?: boolean } | undefined ,
955+ ] ;
956+ expect ( optionsArg ) . toEqual (
957+ expect . objectContaining ( {
958+ model : "openai:gpt-4o" ,
959+ agentId : "exec" ,
960+ thinkingLevel : "high" ,
961+ } ) as SendMessageOptions
962+ ) ;
963+ expect ( optionsArg . toolPolicy ) . toBeUndefined ( ) ;
964+ expect ( internalArg ?. agentInitiated ) . toBeUndefined ( ) ;
965+
966+ session . dispose ( ) ;
967+ } ) ;
968+
875969 test ( "persists startup abandon marker for pre-stream user aborts" , async ( ) => {
876970 const workspaceId = "startup-retry-pre-stream-abort" ;
877971 const { historyService, config, cleanup } = await createTestHistoryService ( ) ;
0 commit comments