@@ -330,6 +330,101 @@ async fn test_pre_planning_hook_modification_updates_planner_input() {
330330 assert ! ( post_planning_task. contains( "Hook-modified planning task" ) ) ;
331331}
332332
333+ #[ tokio:: test]
334+ async fn test_pre_planning_hook_modification_discards_pre_analysis_plan ( ) {
335+ use crate :: planning:: { AgentGoal , Complexity , ExecutionPlan , PreAnalysis , Task } ;
336+ use crate :: prompts:: AgentStyle ;
337+
338+ let mock_client = Arc :: new ( MockLlmClient :: new ( vec ! [
339+ MockLlmClient :: text_response(
340+ r#"{
341+ "goal": "Use the hook-modified plan",
342+ "complexity": "Simple",
343+ "steps": [
344+ {
345+ "id": "step-1",
346+ "description": "Execute the hook-modified plan",
347+ "dependencies": [],
348+ "success_criteria": "The pre-analysis plan is not reused"
349+ }
350+ ],
351+ "required_tools": []
352+ }"# ,
353+ ) ,
354+ MockLlmClient :: text_response( "Hook-modified plan executed." ) ,
355+ ] ) ) ;
356+ let tool_executor = Arc :: new ( ToolExecutor :: new ( "/tmp" . to_string ( ) ) ) ;
357+ let ( hook, events) =
358+ planning_hook_recorder ( crate :: hooks:: HookResult :: continue_with ( serde_json:: json!( {
359+ "modified_task" : "Use the hook-modified plan instead"
360+ } ) ) ) ;
361+ let config = AgentConfig {
362+ hook_engine : Some ( hook) ,
363+ ..AgentConfig :: default ( )
364+ } ;
365+ let agent = AgentLoop :: new (
366+ mock_client. clone ( ) ,
367+ tool_executor,
368+ test_tool_context ( ) ,
369+ config,
370+ ) ;
371+
372+ let mut stale_plan = ExecutionPlan :: new ( "Stale pre-analysis plan" , Complexity :: Simple ) ;
373+ stale_plan. add_step ( Task :: new (
374+ "stale-step" ,
375+ "This stale pre-analysis step must not run" ,
376+ ) ) ;
377+ let pre_analysis = PreAnalysis {
378+ intent : AgentStyle :: GeneralPurpose ,
379+ requires_planning : true ,
380+ goal : AgentGoal :: new ( "Stale pre-analysis goal" ) ,
381+ execution_plan : stale_plan,
382+ optimized_input : "Stale optimized input" . to_string ( ) ,
383+ } ;
384+
385+ let result = agent
386+ . execute_with_planning (
387+ & [ ] ,
388+ "Original request with stale pre-analysis" ,
389+ Some ( "discard-pre-analysis-session" ) ,
390+ None ,
391+ Some ( pre_analysis) ,
392+ )
393+ . await
394+ . unwrap ( ) ;
395+
396+ assert_eq ! ( result. text, "Hook-modified plan executed." ) ;
397+
398+ let request_texts = mock_client. request_texts . lock ( ) . unwrap ( ) . clone ( ) ;
399+ assert ! (
400+ request_texts[ 0 ]
401+ . contains( "Hook-modified planning task:\n Use the hook-modified plan instead" ) ,
402+ "planner input should be regenerated from the hook-modified task: {}" ,
403+ request_texts[ 0 ]
404+ ) ;
405+ assert ! (
406+ !request_texts
407+ . iter( )
408+ . any( |text| text. contains( "This stale pre-analysis step must not run" ) ) ,
409+ "stale pre-analysis step should not be sent to execution: {:?}" ,
410+ request_texts
411+ ) ;
412+
413+ let post_planning_subtasks = events
414+ . lock ( )
415+ . unwrap ( )
416+ . iter ( )
417+ . find_map ( |event| match event {
418+ crate :: hooks:: HookEvent :: PostPlanning ( event) => Some ( event. subtasks . clone ( ) ) ,
419+ _ => None ,
420+ } )
421+ . expect ( "PostPlanning should be emitted" ) ;
422+ assert_eq ! (
423+ post_planning_subtasks,
424+ vec![ "Execute the hook-modified plan" . to_string( ) ]
425+ ) ;
426+ }
427+
333428// ========================================================================
334429// AgentEvent serialization
335430// ========================================================================
0 commit comments