@@ -13,7 +13,8 @@ use warpui::AppContext;
1313use warpui:: SingletonEntity ;
1414
1515use crate :: ai:: agent:: conversation:: ConversationStatus ;
16- use crate :: ai:: agent_conversations_model:: ConversationOrTask ;
16+ use crate :: ai:: agent_conversations_model:: { AgentConversationsModel , ConversationOrTask } ;
17+ use crate :: ai:: blocklist:: BlocklistAIHistoryModel ;
1718use crate :: terminal:: cli_agent_sessions:: listener:: agent_supports_rich_status;
1819use crate :: terminal:: cli_agent_sessions:: CLIAgentSessionsModel ;
1920use crate :: terminal:: view:: TerminalView ;
@@ -24,26 +25,48 @@ use crate::ui_components::icon_with_status::IconWithStatusVariant;
2425/// not an agent surface (plain terminal / shell / empty conversation).
2526///
2627/// Resolution order:
27- /// 1. A [`CLIAgentSessionsModel`] session with a known agent (observed reality) wins.
28- /// Plugin-backed sessions surface rich status; command-detected sessions don't.
29- /// 2. An ambient agent with a selected third-party harness uses the harness's CLI brand
30- /// even before the harness CLI has started running in the sandbox.
31- /// 3. A selected conversation or ambient Oz run falls back to the Oz agent variant.
28+ /// 1. A [`CLIAgentSessionsModel`] session with a known agent wins. Plugin-backed sessions
29+ /// surface rich status; command-detected sessions don't.
30+ /// 2. A task-backed run defers to [`conversation_or_task_agent_icon_variant`] so the
31+ /// terminal chrome and the matching conversation list card stay in lockstep.
32+ /// 3. Live ambient pre-dispatch or a selected local conversation falls through to the
33+ /// no-task waterfall.
3234/// 4. Everything else returns `None` so the caller renders a plain-terminal indicator.
3335pub ( crate ) fn terminal_view_agent_icon_variant (
3436 terminal_view : & TerminalView ,
3537 app : & AppContext ,
3638) -> Option < IconWithStatusVariant > {
3739 let cli_agent_session = CLIAgentSessionsModel :: as_ref ( app) . session ( terminal_view. id ( ) ) ;
40+
41+ // Resolve the ambient task id from [`TerminalView::ambient_agent_task_id_for_details_panel`],
42+ // falling back to the selected conversation's server metadata for restored cloud transcripts.
43+ let ambient_task_id = terminal_view
44+ . ambient_agent_task_id_for_details_panel ( app)
45+ . or_else ( || {
46+ terminal_view
47+ . selected_conversation_server_metadata ( app)
48+ . and_then ( |m| m. ambient_agent_task_id )
49+ } ) ;
50+ let task_data = ambient_task_id
51+ . and_then ( |task_id| AgentConversationsModel :: as_ref ( app) . get_task_data ( & task_id) ) ;
52+
53+ // Defer to the card helper when we have task data and no CLI session takes precedence.
54+ if cli_agent_session. is_none ( ) {
55+ if let Some ( task) = task_data. as_ref ( ) {
56+ return conversation_or_task_agent_icon_variant ( & ConversationOrTask :: Task ( task) , app) ;
57+ }
58+ }
59+
60+ let is_ambient = terminal_view. is_ambient_agent_session ( app) || ambient_task_id. is_some ( ) ;
3861 let inputs = TerminalIconInputs {
39- is_ambient : terminal_view . is_ambient_agent_session ( app ) ,
62+ is_ambient,
4063 cli_session : cli_agent_session. map ( |session| CLISessionInputs {
4164 agent : session. agent ,
4265 has_listener : session. listener . is_some ( ) ,
4366 status : session. status . to_conversation_status ( ) ,
4467 supports_rich_status : agent_supports_rich_status ( & session. agent ) ,
4568 } ) ,
46- ambient_selected_third_party_cli_agent : terminal_view
69+ selected_third_party_cli_agent : terminal_view
4770 . ambient_agent_view_model ( )
4871 . and_then ( |model| model. as_ref ( app) . selected_third_party_cli_agent ( ) ) ,
4972 selected_conversation_status : terminal_view. selected_conversation_status_for_display ( app) ,
@@ -56,33 +79,30 @@ pub(crate) fn terminal_view_agent_icon_variant(
5679
5780/// Returns the agent-icon variant for a [`ConversationOrTask`] card row.
5881///
59- /// Task rows resolve their harness from [`ConversationOrTask::harness`]; conversation
60- /// rows have no harness signal and always render as local Oz per the product spec.
82+ /// Both tasks and conversations resolve their harness through [`ConversationOrTask::harness`].
6183pub ( crate ) fn conversation_or_task_agent_icon_variant (
6284 src : & ConversationOrTask < ' _ > ,
6385 app : & AppContext ,
6486) -> Option < IconWithStatusVariant > {
6587 let status = src. status ( app) ;
66- Some ( match src {
67- ConversationOrTask :: Task ( _) => {
68- agent_icon_variant_for_task ( src. harness ( ) . unwrap_or ( Harness :: Oz ) , status)
69- }
70- ConversationOrTask :: Conversation ( _) => IconWithStatusVariant :: OzAgent {
71- status : Some ( status) ,
72- is_ambient : false ,
73- } ,
74- } )
88+ let harness = src. harness ( app) . unwrap_or ( Harness :: Oz ) ;
89+ let is_ambient = match src {
90+ ConversationOrTask :: Task ( _) => true ,
91+ ConversationOrTask :: Conversation ( metadata) => BlocklistAIHistoryModel :: as_ref ( app)
92+ . get_server_conversation_metadata ( & metadata. nav_data . id )
93+ . is_some_and ( |m| m. ambient_agent_task_id . is_some ( ) ) ,
94+ } ;
95+ Some ( agent_icon_variant_for_run ( harness, status, is_ambient) )
7596}
7697
7798/// Primitive inputs to the terminal-view waterfall, gathered once from the live
78- /// [`TerminalView`] / [`AppContext`]. Keeping the decision logic in terms of these
79- /// primitives makes it testable without a live app.
99+ /// [`TerminalView`] / [`AppContext`].
80100struct TerminalIconInputs {
81101 is_ambient : bool ,
82102 cli_session : Option < CLISessionInputs > ,
83- /// The CLI agent corresponding to the currently selected cloud harness, when the selection
84- /// is a third-party (non-Oz) harness . `None` for Oz or when no harness is selected .
85- ambient_selected_third_party_cli_agent : Option < CLIAgent > ,
103+ /// Third-party CLI agent for a live ambient run before task data is available (e.g.
104+ /// Claude pre-dispatch) . `None` otherwise; task-derived harnesses are handled upstream .
105+ selected_third_party_cli_agent : Option < CLIAgent > ,
86106 /// The conversation status that the terminal view would surface in its status-icon slot.
87107 selected_conversation_status : Option < ConversationStatus > ,
88108 /// Whether the terminal view currently has a selected conversation (ambient or local).
@@ -122,13 +142,12 @@ fn agent_icon_variant_from_terminal_inputs(
122142 } ) ;
123143 }
124144
125- // 2. Ambient agent with a selected third-party harness. Render the harness's brand
126- // circle immediately once the user commits, even before the harness CLI starts
127- // running in the sandbox. `Unknown` is filtered to avoid rendering an unbranded
128- // gray circle for a harness this client doesn't recognize.
145+ // 2. Live ambient run with a third-party harness selected, before task data is
146+ // available (e.g. Claude pre-dispatch). `Unknown` is filtered so an unrecognized
147+ // harness doesn't render as an unbranded gray circle.
129148 if inputs. is_ambient {
130149 if let Some ( agent) = inputs
131- . ambient_selected_third_party_cli_agent
150+ . selected_third_party_cli_agent
132151 . filter ( |agent| !matches ! ( agent, CLIAgent :: Unknown ) )
133152 {
134153 return Some ( IconWithStatusVariant :: CLIAgent {
@@ -150,25 +169,26 @@ fn agent_icon_variant_from_terminal_inputs(
150169 None
151170}
152171
153- /// Pure task -card logic: maps a [`Harness`] and the task's current status into an
154- /// [`IconWithStatusVariant`]. Task cards are always ambient. Falls back to the Oz
155- /// variant for [`Harness::Oz`] and [`Harness:: Unknown`], the latter so a future-server
156- /// harness this client doesn't recognize doesn't render an unbranded gray circle.
157- fn agent_icon_variant_for_task (
172+ /// Pure run -card logic: maps a [`Harness`], status, and ambient flag into an
173+ /// [`IconWithStatusVariant`]. Falls back to the Oz variant for [`Harness::Oz`] and
174+ /// [`Harness::Unknown`], the latter so a future-server harness this client doesn't
175+ /// recognize doesn't render an unbranded gray circle.
176+ fn agent_icon_variant_for_run (
158177 harness : Harness ,
159178 status : ConversationStatus ,
179+ is_ambient : bool ,
160180) -> IconWithStatusVariant {
161181 let cli_agent =
162182 CLIAgent :: from_harness ( harness) . filter ( |agent| !matches ! ( agent, CLIAgent :: Unknown ) ) ;
163183 match cli_agent {
164184 Some ( agent) => IconWithStatusVariant :: CLIAgent {
165185 agent,
166186 status : Some ( status) ,
167- is_ambient : true ,
187+ is_ambient,
168188 } ,
169189 None => IconWithStatusVariant :: OzAgent {
170190 status : Some ( status) ,
171- is_ambient : true ,
191+ is_ambient,
172192 } ,
173193 }
174194}
0 commit comments