@@ -610,3 +610,175 @@ async def test_serialization(self) -> None:
610610 self .assertIsNotNone (
611611 agent .plan_notebook .current_plan ,
612612 )
613+
614+ async def test_hint_generator_all_states (self ) -> None :
615+ """Test DefaultPlanToHint covers all plan states."""
616+ from agentscope .plan ._plan_notebook import DefaultPlanToHint
617+
618+ hint_gen = DefaultPlanToHint ()
619+
620+ # State 1: No plan
621+ hint = hint_gen (None )
622+ assert hint is not None
623+ self .assertIn ("create_plan" , hint )
624+
625+ # State 2: All todo
626+ plan = Plan (
627+ name = "Test" ,
628+ description = "desc" ,
629+ expected_outcome = "outcome" ,
630+ subtasks = [
631+ SubTask (name = "t1" , description = "d" , expected_outcome = "e" ),
632+ ],
633+ )
634+ hint = hint_gen (plan )
635+ assert hint is not None
636+ self .assertIn ("subtask_idx=0" , hint )
637+
638+ # State 3: In progress
639+ plan .subtasks [0 ].state = "in_progress"
640+ hint = hint_gen (plan )
641+ assert hint is not None
642+ self .assertIn ("finish_subtask" , hint )
643+
644+ # State 4: Some done, none in progress
645+ plan .subtasks [0 ].state = "done"
646+ plan .subtasks .append (
647+ SubTask (name = "t2" , description = "d" , expected_outcome = "e" ),
648+ )
649+ hint = hint_gen (plan )
650+ assert hint is not None
651+ self .assertIn ("first 1" , hint .lower ())
652+
653+ # State 5: All done
654+ plan .subtasks [1 ].state = "done"
655+ hint = hint_gen (plan )
656+ assert hint is not None
657+ self .assertIn ("finish_plan" , hint )
658+
659+ # State 6: Mix done and abandoned
660+ plan .subtasks [1 ].state = "abandoned"
661+ hint = hint_gen (plan )
662+ assert hint is not None
663+ self .assertIn ("finish_plan" , hint )
664+
665+ async def test_error_paths (self ) -> None :
666+ """Test error handling paths to improve coverage."""
667+ notebook = PlanNotebook ()
668+
669+ # Test operations without plan
670+ with self .assertRaises (ValueError ):
671+ await notebook .revise_current_plan (0 , "delete" )
672+
673+ # Create plan for subsequent tests
674+ await notebook .create_plan (
675+ name = "Test" ,
676+ description = "d" ,
677+ expected_outcome = "e" ,
678+ subtasks = [
679+ SubTask (name = "t1" , description = "d" , expected_outcome = "e" ),
680+ SubTask (name = "t2" , description = "d" , expected_outcome = "e" ),
681+ ],
682+ )
683+
684+ # Invalid types and values
685+ # type: ignore
686+ res = await notebook .revise_current_plan ("invalid" , "delete" )
687+ self .assertIn ("Invalid type" , res .content [0 ].get ("text" , "" ))
688+
689+ # type: ignore
690+ res = await notebook .revise_current_plan (0 , "bad_action" )
691+ self .assertIn ("Invalid action" , res .content [0 ].get ("text" , "" ))
692+
693+ res = await notebook .revise_current_plan (0 , "add" , None )
694+ self .assertIn ("must be provided" , res .content [0 ].get ("text" , "" ))
695+
696+ res = await notebook .revise_current_plan (999 , "delete" )
697+ self .assertIn ("Invalid subtask_idx" , res .content [0 ].get ("text" , "" ))
698+
699+ res = await notebook .update_subtask_state (999 , "in_progress" )
700+ self .assertIn ("Invalid subtask_idx" , res .content [0 ].get ("text" , "" ))
701+
702+ # type: ignore
703+ res = await notebook .update_subtask_state (0 , "bad_state" )
704+ self .assertIn ("Invalid state" , res .content [0 ].get ("text" , "" ))
705+
706+ # State constraints
707+ res = await notebook .update_subtask_state (1 , "in_progress" )
708+ self .assertIn ("not done yet" , res .content [0 ].get ("text" , "" ))
709+
710+ await notebook .update_subtask_state (0 , "in_progress" )
711+ res = await notebook .update_subtask_state (1 , "in_progress" )
712+ self .assertIn ("not done yet" , res .content [0 ].get ("text" , "" ))
713+
714+ res = await notebook .finish_subtask (1 , "outcome" )
715+ self .assertIn ("not done yet" , res .content [0 ].get ("text" , "" ))
716+
717+ async def test_edge_cases (self ) -> None :
718+ """Test edge cases and special scenarios."""
719+ notebook = PlanNotebook ()
720+
721+ # Finish plan when no plan exists
722+ res = await notebook .finish_plan ("done" , "outcome" )
723+ self .assertIn ("no plan" , res .content [0 ].get ("text" , "" ).lower ())
724+
725+ # Create and replace plan
726+ await notebook .create_plan (
727+ "P1" ,
728+ "d" ,
729+ "e" ,
730+ [SubTask (name = "t" , description = "d" , expected_outcome = "e" )],
731+ )
732+ res = await notebook .create_plan (
733+ "P2" ,
734+ "d" ,
735+ "e" ,
736+ [SubTask (name = "t" , description = "d" , expected_outcome = "e" )],
737+ )
738+ self .assertIn ("replaced" , res .content [0 ].get ("text" , "" ).lower ())
739+
740+ # Auto-activate next subtask
741+ assert notebook .current_plan is not None
742+ notebook .current_plan .subtasks .append (
743+ SubTask (name = "t2" , description = "d" , expected_outcome = "e" ),
744+ )
745+ await notebook .update_subtask_state (0 , "in_progress" )
746+ res = await notebook .finish_subtask (0 , "done" )
747+ self .assertIn ("next subtask" , res .content [0 ].get ("text" , "" ).lower ())
748+ assert notebook .current_plan is not None
749+ self .assertEqual (
750+ notebook .current_plan .subtasks [1 ].state ,
751+ "in_progress" ,
752+ )
753+
754+ # Historical plans
755+ await notebook .finish_subtask (1 , "done" )
756+ await notebook .finish_plan ("done" , "final" )
757+ res = await notebook .view_historical_plans ()
758+ self .assertIn ("P2" , res .content [0 ].get ("text" , "" ))
759+
760+ plans = await notebook .storage .get_plans ()
761+ res = await notebook .recover_historical_plan (plans [0 ].id )
762+ self .assertIn ("recovered" , res .content [0 ].get ("text" , "" ).lower ())
763+
764+ res = await notebook .recover_historical_plan ("bad_id" )
765+ self .assertIn ("cannot find" , res .content [0 ].get ("text" , "" ).lower ())
766+
767+ # Hooks
768+ called = []
769+
770+ async def hook (_nb : PlanNotebook , _p : Plan | None ) -> None :
771+ called .append (True )
772+
773+ notebook .register_plan_change_hook ("test" , hook )
774+ await notebook .create_plan (
775+ "P3" ,
776+ "d" ,
777+ "e" ,
778+ [SubTask (name = "t" , description = "d" , expected_outcome = "e" )],
779+ )
780+ self .assertTrue (called )
781+
782+ notebook .remove_plan_change_hook ("test" )
783+ with self .assertRaises (ValueError ):
784+ notebook .remove_plan_change_hook ("bad_hook" )
0 commit comments