@@ -242,11 +242,15 @@ vi.mock("@utils/queryClient", () => ({
242242vi . mock ( "@shared/utils/urls" , ( ) => ( {
243243 getCloudUrlFromRegion : ( ) => "https://api.anthropic.com" ,
244244} ) ) ;
245+ const mockConvertStoredEntriesToEvents = vi . hoisted ( ( ) =>
246+ vi . fn < ( entries : unknown [ ] ) => unknown [ ] > ( ( ) => [ ] ) ,
247+ ) ;
248+
245249vi . mock ( "@utils/session" , async ( ) => {
246250 const actual =
247251 await vi . importActual < typeof import ( "@utils/session" ) > ( "@utils/session" ) ;
248252 return {
249- convertStoredEntriesToEvents : vi . fn ( ( ) => [ ] ) ,
253+ convertStoredEntriesToEvents : mockConvertStoredEntriesToEvents ,
250254 createUserPromptEvent : vi . fn ( ( prompt , ts ) => ( {
251255 type : "acp_message" ,
252256 ts,
@@ -319,6 +323,7 @@ const createMockSession = (
319323describe ( "SessionService" , ( ) => {
320324 beforeEach ( ( ) => {
321325 vi . clearAllMocks ( ) ;
326+ mockConvertStoredEntriesToEvents . mockImplementation ( ( ) => [ ] ) ;
322327 resetSessionService ( ) ;
323328 mockSettingsState . customInstructions = "" ;
324329 mockGetIsOnline . mockReturnValue ( true ) ;
@@ -716,6 +721,119 @@ describe("SessionService", () => {
716721 } ) ;
717722 } ) ;
718723
724+ it ( "flips isPromptPending on hydration when the log tail has an in-flight prompt" , async ( ) => {
725+ const service = getSessionService ( ) ;
726+ const hydratedSession = createMockSession ( {
727+ taskRunId : "run-123" ,
728+ taskId : "task-123" ,
729+ status : "disconnected" ,
730+ isCloud : true ,
731+ events : [ ] ,
732+ } ) ;
733+ mockSessionStoreSetters . getSessionByTaskId . mockReturnValue (
734+ hydratedSession ,
735+ ) ;
736+ mockTrpcLogs . readLocalLogs . query . mockResolvedValue ( "" ) ;
737+ mockTrpcLogs . fetchS3Logs . query . mockResolvedValue ( "{}" ) ;
738+ mockTrpcLogs . writeLocalLogs . mutate . mockResolvedValue ( undefined ) ;
739+
740+ const inFlightPrompt = {
741+ type : "acp_message" as const ,
742+ ts : 1700000000 ,
743+ message : {
744+ jsonrpc : "2.0" as const ,
745+ id : 42 ,
746+ method : "session/prompt" ,
747+ params : { prompt : [ { type : "text" , text : "hi" } ] } ,
748+ } ,
749+ } ;
750+ mockConvertStoredEntriesToEvents . mockReturnValueOnce ( [ inFlightPrompt ] ) ;
751+
752+ service . watchCloudTask (
753+ "task-123" ,
754+ "run-123" ,
755+ "https://api.anthropic.com" ,
756+ 123 ,
757+ undefined ,
758+ "https://logs.example.com/run-123" ,
759+ ) ;
760+
761+ await vi . waitFor ( ( ) => {
762+ expect ( mockSessionStoreSetters . updateSession ) . toHaveBeenCalledWith (
763+ "run-123" ,
764+ expect . objectContaining ( {
765+ isPromptPending : true ,
766+ promptStartedAt : inFlightPrompt . ts ,
767+ currentPromptId : 42 ,
768+ } ) ,
769+ ) ;
770+ } ) ;
771+ } ) ;
772+
773+ it ( "leaves isPromptPending false on hydration when the log tail has a completed prompt" , async ( ) => {
774+ const service = getSessionService ( ) ;
775+ const hydratedSession = createMockSession ( {
776+ taskRunId : "run-123" ,
777+ taskId : "task-123" ,
778+ status : "disconnected" ,
779+ isCloud : true ,
780+ events : [ ] ,
781+ } ) ;
782+ mockSessionStoreSetters . getSessionByTaskId . mockReturnValue (
783+ hydratedSession ,
784+ ) ;
785+ mockSessionStoreSetters . getSessions . mockReturnValue ( {
786+ "run-123" : { ...hydratedSession , currentPromptId : 42 } ,
787+ } ) ;
788+ mockTrpcLogs . readLocalLogs . query . mockResolvedValue ( "" ) ;
789+ mockTrpcLogs . fetchS3Logs . query . mockResolvedValue ( "{}" ) ;
790+ mockTrpcLogs . writeLocalLogs . mutate . mockResolvedValue ( undefined ) ;
791+
792+ const promptRequest = {
793+ type : "acp_message" as const ,
794+ ts : 1700000000 ,
795+ message : {
796+ jsonrpc : "2.0" as const ,
797+ id : 42 ,
798+ method : "session/prompt" ,
799+ params : { prompt : [ { type : "text" , text : "hi" } ] } ,
800+ } ,
801+ } ;
802+ const promptResponse = {
803+ type : "acp_message" as const ,
804+ ts : 1700000005 ,
805+ message : {
806+ jsonrpc : "2.0" as const ,
807+ id : 42 ,
808+ result : { stopReason : "end_turn" } ,
809+ } ,
810+ } ;
811+ mockConvertStoredEntriesToEvents . mockReturnValueOnce ( [
812+ promptRequest ,
813+ promptResponse ,
814+ ] ) ;
815+
816+ service . watchCloudTask (
817+ "task-123" ,
818+ "run-123" ,
819+ "https://api.anthropic.com" ,
820+ 123 ,
821+ undefined ,
822+ "https://logs.example.com/run-123" ,
823+ ) ;
824+
825+ await vi . waitFor ( ( ) => {
826+ expect ( mockSessionStoreSetters . updateSession ) . toHaveBeenCalledWith (
827+ "run-123" ,
828+ expect . objectContaining ( {
829+ isPromptPending : false ,
830+ promptStartedAt : null ,
831+ currentPromptId : null ,
832+ } ) ,
833+ ) ;
834+ } ) ;
835+ } ) ;
836+
719837 it ( "ignores stale async starts when the same watcher is replaced" , async ( ) => {
720838 const service = getSessionService ( ) ;
721839 let resolveFirstWatchStart ! : ( ) => void ;
0 commit comments