@@ -7,6 +7,9 @@ use crate::protocol::events::{
77use crate :: shared_kernel:: SessionId as DomainSessionId ;
88use crate :: types:: SendMessageData ;
99use agent_client_protocol:: schema:: { ContentBlock , LoadSessionRequest , PromptRequest , SessionId , StopReason } ;
10+ use aionui_api_types:: {
11+ AgentErrorCode , AgentErrorOwnership , AgentErrorResolution , AgentErrorResolutionKind , AgentErrorResolutionTarget ,
12+ } ;
1013use aionui_common:: AppError ;
1114use serde_json:: Value ;
1215use tokio:: sync:: broadcast:: error:: TryRecvError ;
@@ -259,13 +262,9 @@ impl AcpAgentManager {
259262 // user already initiated the cancel and doesn't need a second
260263 // notification.
261264 if !matches ! ( prompt_response. stop_reason, StopReason :: Cancelled ) && is_empty_turn ( & mut probe_rx) {
262- self . runtime . emit ( AgentStreamEvent :: Error ( ErrorEventData {
263- // TODO(i18n): wire to a frontend translation key once a
264- // pattern is established. For now this is the user-facing
265- // English string.
266- message : empty_finish_diagnostic_message ( prompt_response. stop_reason ) ,
267- code : Some ( "acp.empty_finish" . into ( ) ) ,
268- } ) ) ;
265+ self . runtime . emit ( AgentStreamEvent :: Error ( empty_finish_diagnostic_error (
266+ prompt_response. stop_reason ,
267+ ) ) ) ;
269268 }
270269
271270 // Emit Finish event
@@ -377,6 +376,24 @@ fn event_is_user_visible_output(event: &AgentStreamEvent) -> bool {
377376 )
378377}
379378
379+ fn empty_finish_diagnostic_error ( stop_reason : StopReason ) -> ErrorEventData {
380+ ErrorEventData :: classified (
381+ // TODO(i18n): wire to a frontend translation key once a
382+ // pattern is established. For now this is the user-facing
383+ // English string.
384+ empty_finish_diagnostic_message ( stop_reason) ,
385+ AgentErrorCode :: UnknownUpstreamError ,
386+ AgentErrorOwnership :: UnknownUpstream ,
387+ Some ( "Agent completed the turn without producing visible output." . into ( ) ) ,
388+ true ,
389+ true ,
390+ Some ( AgentErrorResolution :: new (
391+ AgentErrorResolutionKind :: SendFeedback ,
392+ Some ( AgentErrorResolutionTarget :: Feedback ) ,
393+ ) ) ,
394+ )
395+ }
396+
380397/// Build the user-facing message shown when the agent finished a turn
381398/// without emitting any output. Wording is deliberately concrete so the
382399/// user has something to act on (retry, reword, check provider).
@@ -580,6 +597,7 @@ mod tests {
580597 ToolCallStatus ,
581598 } ;
582599 use agent_client_protocol:: schema:: StopReason ;
600+ use aionui_api_types:: { AgentErrorResolutionKind , AgentErrorResolutionTarget } ;
583601 use tokio:: sync:: broadcast;
584602
585603 /// Lifecycle-only events (`Start`/`Finish`) must NOT count as
@@ -671,4 +689,15 @@ mod tests {
671689 let refusal = super :: empty_finish_diagnostic_message ( StopReason :: Refusal ) ;
672690 assert ! ( refusal. to_lowercase( ) . contains( "refused" ) ) ;
673691 }
692+
693+ #[ test]
694+ fn empty_finish_diagnostic_error_has_feedback_resolution ( ) {
695+ let error = super :: empty_finish_diagnostic_error ( StopReason :: EndTurn ) ;
696+
697+ let resolution = error
698+ . resolution
699+ . expect ( "empty-finish classified errors must include a resolution" ) ;
700+ assert_eq ! ( resolution. kind, AgentErrorResolutionKind :: SendFeedback ) ;
701+ assert_eq ! ( resolution. target, Some ( AgentErrorResolutionTarget :: Feedback ) ) ;
702+ }
674703}
0 commit comments