@@ -539,4 +539,224 @@ describe("StreamingMessageAggregator - Agent Status", () => {
539539 expect ( status ) . toEqual ( { emoji : "✅" , message : truncatedMessage } ) ;
540540 expect ( status ?. message . length ) . toBe ( 60 ) ;
541541 } ) ;
542+
543+ it ( "should store URL when provided in status_set" , ( ) => {
544+ const aggregator = new StreamingMessageAggregator ( "2024-01-01T00:00:00.000Z" ) ;
545+ const messageId = "msg1" ;
546+ const toolCallId = "tool1" ;
547+
548+ // Start a stream
549+ aggregator . handleStreamStart ( {
550+ type : "stream-start" ,
551+ workspaceId : "workspace1" ,
552+ messageId,
553+ model : "test-model" ,
554+ historySequence : 1 ,
555+ } ) ;
556+
557+ // Add a status_set tool call with URL
558+ const testUrl = "https://github.com/owner/repo/pull/123" ;
559+ aggregator . handleToolCallStart ( {
560+ type : "tool-call-start" ,
561+ workspaceId : "workspace1" ,
562+ messageId,
563+ toolCallId,
564+ toolName : "status_set" ,
565+ args : { emoji : "🔗" , message : "PR submitted" , url : testUrl } ,
566+ tokens : 10 ,
567+ timestamp : Date . now ( ) ,
568+ } ) ;
569+
570+ // Complete the tool call
571+ aggregator . handleToolCallEnd ( {
572+ type : "tool-call-end" ,
573+ workspaceId : "workspace1" ,
574+ messageId,
575+ toolCallId,
576+ toolName : "status_set" ,
577+ result : { success : true , emoji : "🔗" , message : "PR submitted" , url : testUrl } ,
578+ } ) ;
579+
580+ const status = aggregator . getAgentStatus ( ) ;
581+ expect ( status ) . toBeDefined ( ) ;
582+ expect ( status ?. emoji ) . toBe ( "🔗" ) ;
583+ expect ( status ?. message ) . toBe ( "PR submitted" ) ;
584+ expect ( status ?. url ) . toBe ( testUrl ) ;
585+ } ) ;
586+
587+ it ( "should persist URL across status updates until explicitly replaced" , ( ) => {
588+ const aggregator = new StreamingMessageAggregator ( "2024-01-01T00:00:00.000Z" ) ;
589+ const messageId = "msg1" ;
590+
591+ // Start a stream
592+ aggregator . handleStreamStart ( {
593+ type : "stream-start" ,
594+ workspaceId : "workspace1" ,
595+ messageId,
596+ model : "test-model" ,
597+ historySequence : 1 ,
598+ } ) ;
599+
600+ // First status with URL
601+ const testUrl = "https://github.com/owner/repo/pull/123" ;
602+ aggregator . handleToolCallStart ( {
603+ type : "tool-call-start" ,
604+ workspaceId : "workspace1" ,
605+ messageId,
606+ toolCallId : "tool1" ,
607+ toolName : "status_set" ,
608+ args : { emoji : "🔗" , message : "PR submitted" , url : testUrl } ,
609+ tokens : 10 ,
610+ timestamp : Date . now ( ) ,
611+ } ) ;
612+
613+ aggregator . handleToolCallEnd ( {
614+ type : "tool-call-end" ,
615+ workspaceId : "workspace1" ,
616+ messageId,
617+ toolCallId : "tool1" ,
618+ toolName : "status_set" ,
619+ result : { success : true , emoji : "🔗" , message : "PR submitted" , url : testUrl } ,
620+ } ) ;
621+
622+ expect ( aggregator . getAgentStatus ( ) ?. url ) . toBe ( testUrl ) ;
623+
624+ // Second status without URL - should keep previous URL
625+ aggregator . handleToolCallStart ( {
626+ type : "tool-call-start" ,
627+ workspaceId : "workspace1" ,
628+ messageId,
629+ toolCallId : "tool2" ,
630+ toolName : "status_set" ,
631+ args : { emoji : "✅" , message : "Done" } ,
632+ tokens : 10 ,
633+ timestamp : Date . now ( ) ,
634+ } ) ;
635+
636+ aggregator . handleToolCallEnd ( {
637+ type : "tool-call-end" ,
638+ workspaceId : "workspace1" ,
639+ messageId,
640+ toolCallId : "tool2" ,
641+ toolName : "status_set" ,
642+ result : { success : true , emoji : "✅" , message : "Done" } ,
643+ } ) ;
644+
645+ const statusAfterUpdate = aggregator . getAgentStatus ( ) ;
646+ expect ( statusAfterUpdate ?. emoji ) . toBe ( "✅" ) ;
647+ expect ( statusAfterUpdate ?. message ) . toBe ( "Done" ) ;
648+ expect ( statusAfterUpdate ?. url ) . toBe ( testUrl ) ; // URL persists
649+
650+ // Third status with different URL - should replace
651+ const newUrl = "https://github.com/owner/repo/pull/456" ;
652+ aggregator . handleToolCallStart ( {
653+ type : "tool-call-start" ,
654+ workspaceId : "workspace1" ,
655+ messageId,
656+ toolCallId : "tool3" ,
657+ toolName : "status_set" ,
658+ args : { emoji : "🔄" , message : "New PR" , url : newUrl } ,
659+ tokens : 10 ,
660+ timestamp : Date . now ( ) ,
661+ } ) ;
662+
663+ aggregator . handleToolCallEnd ( {
664+ type : "tool-call-end" ,
665+ workspaceId : "workspace1" ,
666+ messageId,
667+ toolCallId : "tool3" ,
668+ toolName : "status_set" ,
669+ result : { success : true , emoji : "🔄" , message : "New PR" , url : newUrl } ,
670+ } ) ;
671+
672+ const finalStatus = aggregator . getAgentStatus ( ) ;
673+ expect ( finalStatus ?. emoji ) . toBe ( "🔄" ) ;
674+ expect ( finalStatus ?. message ) . toBe ( "New PR" ) ;
675+ expect ( finalStatus ?. url ) . toBe ( newUrl ) ; // URL replaced
676+ } ) ;
677+
678+ it ( "should persist URL even after status is cleared by new stream start" , ( ) => {
679+ const aggregator = new StreamingMessageAggregator ( "2024-01-01T00:00:00.000Z" ) ;
680+ const messageId1 = "msg1" ;
681+
682+ // Start first stream
683+ aggregator . handleStreamStart ( {
684+ type : "stream-start" ,
685+ workspaceId : "workspace1" ,
686+ messageId : messageId1 ,
687+ model : "test-model" ,
688+ historySequence : 1 ,
689+ } ) ;
690+
691+ // Set status with URL in first stream
692+ const testUrl = "https://github.com/owner/repo/pull/123" ;
693+ aggregator . handleToolCallStart ( {
694+ type : "tool-call-start" ,
695+ workspaceId : "workspace1" ,
696+ messageId : messageId1 ,
697+ toolCallId : "tool1" ,
698+ toolName : "status_set" ,
699+ args : { emoji : "🔗" , message : "PR submitted" , url : testUrl } ,
700+ tokens : 10 ,
701+ timestamp : Date . now ( ) ,
702+ } ) ;
703+
704+ aggregator . handleToolCallEnd ( {
705+ type : "tool-call-end" ,
706+ workspaceId : "workspace1" ,
707+ messageId : messageId1 ,
708+ toolCallId : "tool1" ,
709+ toolName : "status_set" ,
710+ result : { success : true , emoji : "🔗" , message : "PR submitted" , url : testUrl } ,
711+ } ) ;
712+
713+ expect ( aggregator . getAgentStatus ( ) ?. url ) . toBe ( testUrl ) ;
714+
715+ // User sends a new message, which clears the status
716+ const userMessage = {
717+ id : "user1" ,
718+ role : "user" as const ,
719+ parts : [ { type : "text" as const , text : "Continue" } ] ,
720+ metadata : { timestamp : Date . now ( ) , historySequence : 2 } ,
721+ } ;
722+ aggregator . handleMessage ( userMessage ) ;
723+
724+ expect ( aggregator . getAgentStatus ( ) ) . toBeUndefined ( ) ; // Status cleared
725+
726+ // Start second stream
727+ const messageId2 = "msg2" ;
728+ aggregator . handleStreamStart ( {
729+ type : "stream-start" ,
730+ workspaceId : "workspace1" ,
731+ messageId : messageId2 ,
732+ model : "test-model" ,
733+ historySequence : 2 ,
734+ } ) ;
735+
736+ // Set new status WITHOUT URL - should use the last URL ever seen
737+ aggregator . handleToolCallStart ( {
738+ type : "tool-call-start" ,
739+ workspaceId : "workspace1" ,
740+ messageId : messageId2 ,
741+ toolCallId : "tool2" ,
742+ toolName : "status_set" ,
743+ args : { emoji : "✅" , message : "Tests passed" } ,
744+ tokens : 10 ,
745+ timestamp : Date . now ( ) ,
746+ } ) ;
747+
748+ aggregator . handleToolCallEnd ( {
749+ type : "tool-call-end" ,
750+ workspaceId : "workspace1" ,
751+ messageId : messageId2 ,
752+ toolCallId : "tool2" ,
753+ toolName : "status_set" ,
754+ result : { success : true , emoji : "✅" , message : "Tests passed" } ,
755+ } ) ;
756+
757+ const finalStatus = aggregator . getAgentStatus ( ) ;
758+ expect ( finalStatus ?. emoji ) . toBe ( "✅" ) ;
759+ expect ( finalStatus ?. message ) . toBe ( "Tests passed" ) ;
760+ expect ( finalStatus ?. url ) . toBe ( testUrl ) ; // URL from previous stream persists!
761+ } ) ;
542762} ) ;
0 commit comments