@@ -1595,6 +1595,7 @@ impl AgentLoop {
15951595 session_id,
15961596 event_tx,
15971597 token,
1598+ true , // emit_end: this is a standalone execution
15981599 )
15991600 . await ;
16001601
@@ -1682,7 +1683,7 @@ impl AgentLoop {
16821683 let result = if use_planning {
16831684 self . execute_with_planning ( history, prompt, event_tx) . await
16841685 } else {
1685- self . execute_loop ( history, prompt, session_id, event_tx, token)
1686+ self . execute_loop ( history, prompt, session_id, event_tx, token, true )
16861687 . await
16871688 } ;
16881689
@@ -1753,17 +1754,28 @@ impl AgentLoop {
17531754 session_id : Option < & str > ,
17541755 event_tx : Option < mpsc:: Sender < AgentEvent > > ,
17551756 cancel_token : & tokio_util:: sync:: CancellationToken ,
1757+ emit_end : bool ,
17561758 ) -> Result < AgentResult > {
17571759 // When called via execute_loop, the prompt is used for both
17581760 // message-adding and hook/memory/event purposes.
1759- self . execute_loop_inner ( history, prompt, prompt, session_id, event_tx, cancel_token)
1760- . await
1761+ self . execute_loop_inner (
1762+ history,
1763+ prompt,
1764+ prompt,
1765+ session_id,
1766+ event_tx,
1767+ cancel_token,
1768+ emit_end,
1769+ )
1770+ . await
17611771 }
17621772
17631773 /// Inner execution loop.
17641774 ///
17651775 /// `msg_prompt` controls whether a user message is appended (empty = skip).
17661776 /// `effective_prompt` is used for hooks, memory recall, taint tracking, and events.
1777+ /// `emit_end` controls whether to send `AgentEvent::End` when the loop completes
1778+ /// (should be false when called from `execute_plan` to avoid duplicate End events).
17671779 async fn execute_loop_inner (
17681780 & self ,
17691781 history : & [ Message ] ,
@@ -1772,6 +1784,7 @@ impl AgentLoop {
17721784 session_id : Option < & str > ,
17731785 event_tx : Option < mpsc:: Sender < AgentEvent > > ,
17741786 cancel_token : & tokio_util:: sync:: CancellationToken ,
1787+ emit_end : bool ,
17751788 ) -> Result < AgentResult > {
17761789 let mut messages = history. to_vec ( ) ;
17771790 let mut total_usage = TokenUsage :: default ( ) ;
@@ -2202,14 +2215,16 @@ impl AgentLoop {
22022215 "Agent execution completed"
22032216 ) ;
22042217
2205- if let Some ( tx) = & event_tx {
2206- tx. send ( AgentEvent :: End {
2207- text : final_text. clone ( ) ,
2208- usage : total_usage. clone ( ) ,
2209- meta : response. meta . clone ( ) ,
2210- } )
2211- . await
2212- . ok ( ) ;
2218+ if emit_end {
2219+ if let Some ( tx) = & event_tx {
2220+ tx. send ( AgentEvent :: End {
2221+ text : final_text. clone ( ) ,
2222+ usage : total_usage. clone ( ) ,
2223+ meta : response. meta . clone ( ) ,
2224+ } )
2225+ . await
2226+ . ok ( ) ;
2227+ }
22132228 }
22142229
22152230 // Notify context providers of turn completion for memory extraction
@@ -2930,6 +2945,17 @@ impl AgentLoop {
29302945 // Execute the plan step by step
29312946 let result = self . execute_plan ( history, & plan, event_tx. clone ( ) ) . await ?;
29322947
2948+ // Emit the final End event (execute_loop_inner does not emit End in planning mode)
2949+ if let Some ( tx) = & event_tx {
2950+ tx. send ( AgentEvent :: End {
2951+ text : result. text . clone ( ) ,
2952+ usage : result. usage . clone ( ) ,
2953+ meta : None ,
2954+ } )
2955+ . await
2956+ . ok ( ) ;
2957+ }
2958+
29332959 // Check goal achievement when goal_tracking is enabled
29342960 if self . config . goal_tracking {
29352961 if let Some ( ref g) = goal {
@@ -3045,6 +3071,7 @@ impl AgentLoop {
30453071 None ,
30463072 event_tx. clone ( ) ,
30473073 & tokio_util:: sync:: CancellationToken :: new ( ) ,
3074+ false , // emit_end: false — End is emitted by execute_with_planning after execute_plan
30483075 )
30493076 . await
30503077 {
@@ -3139,6 +3166,7 @@ impl AgentLoop {
31393166 None ,
31403167 tx,
31413168 & tokio_util:: sync:: CancellationToken :: new ( ) ,
3169+ false , // emit_end: false — End is emitted by execute_with_planning after execute_plan
31423170 )
31433171 . await ;
31443172 ( step_clone. id , sn, result)
0 commit comments