@@ -28,6 +28,10 @@ use crate::build;
2828use crate :: bus:: { Bus , BusEvent , SubagentStatus , ToolEvent , ToolStatus } ;
2929use crate :: cache_tracker:: CacheTracker ;
3030use crate :: compaction:: CompactionEvent ;
31+ use crate :: hooks:: config:: { HookEvent , load_hooks_config} ;
32+ use crate :: hooks:: execute:: { execute_hook, HookResult } ;
33+ use crate :: hooks:: registry:: { HookContext , HookRegistry } ;
34+ use crate :: hooks:: types:: HookInput ;
3135use crate :: id;
3236use crate :: logging;
3337use crate :: message:: {
@@ -47,6 +51,7 @@ use std::path::PathBuf;
4751use std:: sync:: { Arc , LazyLock , Mutex as StdMutex } ;
4852use std:: time:: { Duration , Instant } ;
4953use tokio:: sync:: { broadcast, mpsc} ;
54+ use tracing:: debug;
5055
5156use interrupts:: { NoToolCallOutcome , PostToolInterruptOutcome } ;
5257pub use jcode_agent_runtime:: {
@@ -324,9 +329,37 @@ impl Agent {
324329 agent. session . parent_id . clone ( ) ,
325330 false ,
326331 ) ;
332+ let session_id = agent. session . id . clone ( ) ;
333+ let cwd = agent. session . working_dir . clone ( ) . unwrap_or_default ( ) ;
334+ tokio:: spawn ( async move {
335+ if let Some ( hook_result) = Self :: execute_session_start_hook ( & session_id, & cwd) . await {
336+ debug ! ( "SessionStart hook executed: {:?}" , hook_result) ;
337+ }
338+ } ) ;
327339 agent
328340 }
329341
342+ async fn execute_session_start_hook ( session_id : & str , cwd : & str ) -> Option < HookResult > {
343+ let config = load_hooks_config ( ) ;
344+ let registry = HookRegistry :: from_config ( config) ;
345+ if registry. is_empty ( ) {
346+ return None ;
347+ }
348+ let hook_ctx = HookContext :: for_session_start ( session_id. to_string ( ) , cwd. to_string ( ) ) ;
349+ let hook_input = HookInput :: for_session_start ( session_id. to_string ( ) , cwd. to_string ( ) ) ;
350+ let matching = registry. get_matching ( & HookEvent :: SessionStart , & hook_ctx) ;
351+ if matching. is_empty ( ) {
352+ return None ;
353+ }
354+ match execute_hook ( matching[ 0 ] , & hook_input) . await {
355+ Ok ( result) => Some ( result) ,
356+ Err ( e) => {
357+ debug ! ( "SessionStart hook error: {}" , e) ;
358+ None
359+ }
360+ }
361+ }
362+
330363 pub fn new_with_session (
331364 provider : Arc < dyn Provider > ,
332365 registry : Registry ,
@@ -813,13 +846,42 @@ impl Agent {
813846 & self . provider . model ( ) ,
814847 crate :: telemetry:: SessionEndReason :: NormalExit ,
815848 ) ;
849+ let session_id = self . session . id . clone ( ) ;
850+ Self :: execute_session_end_hook ( & session_id) ;
816851 self . persist_soft_interrupt_snapshot ( ) ;
817852 self . session . mark_closed ( ) ;
818853 if !self . session . messages . is_empty ( ) {
819854 self . persist_session_best_effort ( "session close state" ) ;
820855 }
821856 }
822857
858+ fn execute_session_end_hook ( session_id : & str ) {
859+ let config = load_hooks_config ( ) ;
860+ let registry = HookRegistry :: from_config ( config) ;
861+ if registry. is_empty ( ) {
862+ return ;
863+ }
864+ let hook_ctx = HookContext :: for_session_end ( session_id. to_string ( ) ) ;
865+ let hook_input = HookInput :: for_session_end ( session_id. to_string ( ) ) ;
866+ let matching = registry. get_matching ( & HookEvent :: SessionEnd , & hook_ctx) ;
867+ for handler in matching {
868+ let runtime = tokio:: runtime:: Handle :: current ( ) ;
869+ let result = runtime. block_on ( execute_hook ( handler, & hook_input) ) ;
870+ match result {
871+ Ok ( HookResult :: Continue ( _) ) => {
872+ debug ! ( "SessionEnd hook completed for {}" , session_id) ;
873+ }
874+ Ok ( HookResult :: Failed { error } ) => {
875+ debug ! ( "SessionEnd hook failed for {}: {}" , session_id, error) ;
876+ }
877+ Err ( e) => {
878+ debug ! ( "SessionEnd hook error for {}: {}" , session_id, e) ;
879+ }
880+ _ => { }
881+ }
882+ }
883+ }
884+
823885 pub fn mark_crashed ( & mut self , message : Option < String > ) {
824886 crate :: telemetry:: record_crash (
825887 self . provider . name ( ) ,
0 commit comments