agent: Add setting to control terminal card expanded state (#34061)

Danilo Leal created

Similar to https://github.com/zed-industries/zed/pull/34040, this PR
allows to control via settings whether the terminal card in the agent
panel should be expanded. It is set to true by default.

Release Notes:

- agent: Added a setting to control whether terminal cards are expanded
in the agent panel, thus showing or hiding the full command output.

Change summary

assets/settings/default.json                |  6 +
crates/agent_settings/src/agent_settings.rs |  9 ++
crates/assistant_tools/src/terminal_tool.rs | 80 ++++++++++++++--------
docs/src/ai/configuration.md                | 16 ++++
4 files changed, 79 insertions(+), 32 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -861,7 +861,11 @@
     /// Whether to have edit cards in the agent panel expanded, showing a preview of the full diff.
     ///
     /// Default: true
-    "expand_edit_card": true
+    "expand_edit_card": true,
+    /// Whether to have terminal cards in the agent panel expanded, showing the whole command output.
+    ///
+    /// Default: true
+    "expand_terminal_card": true
   },
   // The settings for slash commands.
   "slash_commands": {

crates/agent_settings/src/agent_settings.rs 🔗

@@ -68,6 +68,7 @@ pub struct AgentSettings {
     pub preferred_completion_mode: CompletionMode,
     pub enable_feedback: bool,
     pub expand_edit_card: bool,
+    pub expand_terminal_card: bool,
 }
 
 impl AgentSettings {
@@ -296,6 +297,10 @@ pub struct AgentSettingsContent {
     ///
     /// Default: true
     expand_edit_card: Option<bool>,
+    /// Whether to have terminal cards in the agent panel expanded, showing the whole command output.
+    ///
+    /// Default: true
+    expand_terminal_card: Option<bool>,
 }
 
 #[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
@@ -447,6 +452,10 @@ impl Settings for AgentSettings {
             );
             merge(&mut settings.enable_feedback, value.enable_feedback);
             merge(&mut settings.expand_edit_card, value.expand_edit_card);
+            merge(
+                &mut settings.expand_terminal_card,
+                value.expand_terminal_card,
+            );
 
             settings
                 .model_parameters

crates/assistant_tools/src/terminal_tool.rs 🔗

@@ -2,12 +2,13 @@ use crate::{
     schema::json_schema_for,
     ui::{COLLAPSED_LINES, ToolOutputPreview},
 };
+use agent_settings;
 use anyhow::{Context as _, Result, anyhow};
 use assistant_tool::{ActionLog, Tool, ToolCard, ToolResult, ToolUseStatus};
 use futures::{FutureExt as _, future::Shared};
 use gpui::{
-    AnyWindowHandle, App, AppContext, Empty, Entity, EntityId, Task, TextStyleRefinement,
-    WeakEntity, Window,
+    Animation, AnimationExt, AnyWindowHandle, App, AppContext, Empty, Entity, EntityId, Task,
+    TextStyleRefinement, Transformation, WeakEntity, Window, percentage,
 };
 use language::LineEnding;
 use language_model::{LanguageModel, LanguageModelRequest, LanguageModelToolSchemaFormat};
@@ -247,6 +248,7 @@ impl Tool for TerminalTool {
                 command_markdown.clone(),
                 working_dir.clone(),
                 cx.entity_id(),
+                cx,
             )
         });
 
@@ -441,7 +443,10 @@ impl TerminalToolCard {
         input_command: Entity<Markdown>,
         working_dir: Option<PathBuf>,
         entity_id: EntityId,
+        cx: &mut Context<Self>,
     ) -> Self {
+        let expand_terminal_card =
+            agent_settings::AgentSettings::get_global(cx).expand_terminal_card;
         Self {
             input_command,
             working_dir,
@@ -453,7 +458,7 @@ impl TerminalToolCard {
             finished_with_empty_output: false,
             original_content_len: 0,
             content_line_count: 0,
-            preview_expanded: true,
+            preview_expanded: expand_terminal_card,
             start_instant: Instant::now(),
             elapsed_time: None,
         }
@@ -518,6 +523,46 @@ impl ToolCard for TerminalToolCard {
                             .color(Color::Muted),
                     ),
             )
+            .when(!self.command_finished, |header| {
+                header.child(
+                    Icon::new(IconName::ArrowCircle)
+                        .size(IconSize::XSmall)
+                        .color(Color::Info)
+                        .with_animation(
+                            "arrow-circle",
+                            Animation::new(Duration::from_secs(2)).repeat(),
+                            |icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
+                        ),
+                )
+            })
+            .when(tool_failed || command_failed, |header| {
+                header.child(
+                    div()
+                        .id(("terminal-tool-error-code-indicator", self.entity_id))
+                        .child(
+                            Icon::new(IconName::Close)
+                                .size(IconSize::Small)
+                                .color(Color::Error),
+                        )
+                        .when(command_failed && self.exit_status.is_some(), |this| {
+                            this.tooltip(Tooltip::text(format!(
+                                "Exited with code {}",
+                                self.exit_status
+                                    .and_then(|status| status.code())
+                                    .unwrap_or(-1),
+                            )))
+                        })
+                        .when(
+                            !command_failed && tool_failed && status.error().is_some(),
+                            |this| {
+                                this.tooltip(Tooltip::text(format!(
+                                    "Error: {}",
+                                    status.error().unwrap(),
+                                )))
+                            },
+                        ),
+                )
+            })
             .when(self.was_content_truncated, |header| {
                 let tooltip = if self.content_line_count + 10 > terminal::MAX_SCROLL_HISTORY_LINES {
                     "Output exceeded terminal max lines and was \
@@ -555,34 +600,6 @@ impl ToolCard for TerminalToolCard {
                         .size(LabelSize::Small),
                 )
             })
-            .when(tool_failed || command_failed, |header| {
-                header.child(
-                    div()
-                        .id(("terminal-tool-error-code-indicator", self.entity_id))
-                        .child(
-                            Icon::new(IconName::Close)
-                                .size(IconSize::Small)
-                                .color(Color::Error),
-                        )
-                        .when(command_failed && self.exit_status.is_some(), |this| {
-                            this.tooltip(Tooltip::text(format!(
-                                "Exited with code {}",
-                                self.exit_status
-                                    .and_then(|status| status.code())
-                                    .unwrap_or(-1),
-                            )))
-                        })
-                        .when(
-                            !command_failed && tool_failed && status.error().is_some(),
-                            |this| {
-                                this.tooltip(Tooltip::text(format!(
-                                    "Error: {}",
-                                    status.error().unwrap(),
-                                )))
-                            },
-                        ),
-                )
-            })
             .when(!self.finished_with_empty_output, |header| {
                 header.child(
                     Disclosure::new(
@@ -634,6 +651,7 @@ impl ToolCard for TerminalToolCard {
                         div()
                             .pt_2()
                             .border_t_1()
+                            .when(tool_failed || command_failed, |card| card.border_dashed())
                             .border_color(border_color)
                             .bg(cx.theme().colors().editor_background)
                             .rounded_b_md()

docs/src/ai/configuration.md 🔗

@@ -662,3 +662,19 @@ It is set to `true` by default, but if set to false, the card's height is capped
 
 This setting is currently only available in Preview.
 It should be up in Stable by the next release.
+
+### Terminal Card
+
+Use the `expand_terminal_card` setting to control whether terminal cards show the command output in the Agent Panel.
+It is set to `true` by default, but if set to false, the card will be fully collapsed even while the command is running, requiring a click to be expanded.
+
+```json
+{
+  "agent": {
+    "expand_terminal_card": "false"
+  }
+}
+```
+
+This setting is currently only available in Preview.
+It should be up in Stable by the next release.