Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 50 additions & 7 deletions crates/chat-cli/src/cli/chat/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,8 @@ pub struct ChatSession {
prompt_ack_rx: std::sync::mpsc::Receiver<()>,
/// Additional context to be added to the next user message (e.g., delegate task summaries)
pending_additional_context: Option<String>,
/// Tools used in the current turn (for non-verbose completion messages)
tools_used_in_turn: Vec<String>,
}

impl ChatSession {
Expand Down Expand Up @@ -814,6 +816,7 @@ impl ChatSession {
wrap,
prompt_ack_rx,
pending_additional_context: None,
tools_used_in_turn: Vec::new(),
})
}

Expand Down Expand Up @@ -2443,13 +2446,22 @@ impl ChatSession {
}
}

// Determine if we should show detailed output (same logic as print_tool_description)
let verbose = os.database
.settings
.get_bool(Setting::ChatToolOutputVerbose)
.unwrap_or(true);
// tool.accepted is true if the tool was trusted (allowed without permission)
let show_details = verbose || !tool.accepted;

let invoke_result = tool
.tool
.invoke(
os,
&mut self.stdout,
&mut self.conversation.file_line_tracker,
&self.conversation.agents,
show_details,
)
.await;

Expand Down Expand Up @@ -2578,13 +2590,28 @@ impl ChatSession {
}

debug!("tool result output: {:#?}", result);

// Check verbose setting
let verbose = os.database
.settings
.get_bool(Setting::ChatToolOutputVerbose)
.unwrap_or(true);

// Build completion message
let completion_msg = if verbose || self.tools_used_in_turn.is_empty() {
format!(" ● Completed in {}s", tool_time)
} else {
let tools = self.tools_used_in_turn.join(", ");
format!(" ● Completed in {}s ({})", tool_time, tools)
};

execute!(
self.stdout,
style::Print(CONTINUATION_LINE),
style::Print("\n"),
StyledText::success_fg(),
style::SetAttribute(Attribute::Bold),
style::Print(format!(" ● Completed in {}s", tool_time)),
style::Print(completion_msg),
StyledText::reset(),
)?;
if let Some(tag) = checkpoint_tag {
Expand Down Expand Up @@ -3137,6 +3164,7 @@ impl ChatSession {
self.tool_uses.clear();
self.pending_tool_index = None;
self.tool_turn_start_time = None;
self.tools_used_in_turn.clear();

// Create turn checkpoint if tools were used
if ExperimentManager::is_enabled(os, ExperimentName::Checkpoint) && !self.conversation.is_in_tangent_mode()
Expand Down Expand Up @@ -3450,6 +3478,19 @@ impl ChatSession {

async fn print_tool_description(&mut self, os: &Os, tool_index: usize, trusted: bool) -> Result<(), ChatError> {
let tool_use = &self.tool_uses[tool_index];
let tool_name = tool_use.tool.display_name();

// Always track tool usage for completion message
self.tools_used_in_turn.push(tool_name.clone());

// Determine if we should show detailed output
let verbose = os.database
.settings
.get_bool(Setting::ChatToolOutputVerbose)
.unwrap_or(true); // Default to verbose

// Show details if: verbose mode is on OR tool requires permission
let show_details = verbose || !trusted;

if self.stderr.should_send_structured_event {
let tool_call_start = ToolCallStart {
Expand All @@ -3464,7 +3505,7 @@ impl ChatSession {
parent_message_id: None,
};
self.stdout.send(Event::ToolCallStart(tool_call_start))?;
} else {
} else if show_details {
queue!(
self.stdout,
StyledText::emphasis_fg(),
Expand Down Expand Up @@ -3495,11 +3536,13 @@ impl ChatSession {
)?;
}

tool_use
.tool
.queue_description(os, &mut self.stdout)
.await
.map_err(|e| ChatError::Custom(format!("failed to print tool, `{}`: {}", tool_use.name, e).into()))?;
if show_details {
tool_use
.tool
.queue_description(os, &mut self.stdout)
.await
.map_err(|e| ChatError::Custom(format!("failed to print tool, `{}`: {}", tool_use.name, e).into()))?;
}

self.stdout.flush()?;

Expand Down
30 changes: 17 additions & 13 deletions crates/chat-cli/src/cli/chat/tools/fs_read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,10 +266,10 @@ impl FsRead {
}
}

pub async fn invoke(&self, os: &Os, updates: &mut impl Write) -> Result<InvokeOutput> {
pub async fn invoke(&self, os: &Os, updates: &mut impl Write, show_details: bool) -> Result<InvokeOutput> {
if self.operations.len() == 1 {
// Single operation - return result directly
self.operations[0].invoke(os, updates).await
self.operations[0].invoke(os, updates, show_details).await
} else {
// Multiple operations - combine results
let mut combined_results = Vec::new();
Expand All @@ -279,7 +279,7 @@ impl FsRead {
let mut failed_ops = 0usize;

for (i, op) in self.operations.iter().enumerate() {
match op.invoke(os, updates).await {
match op.invoke(os, updates, show_details).await {
Ok(result) => {
success_ops += 1;

Expand Down Expand Up @@ -333,6 +333,7 @@ impl FsRead {
updates,
false,
true,
show_details,
)?;

let combined_text = combined_results.join("\n\n");
Expand Down Expand Up @@ -376,12 +377,12 @@ impl FsReadOperation {
}
}

pub async fn invoke(&self, os: &Os, updates: &mut impl Write) -> Result<InvokeOutput> {
pub async fn invoke(&self, os: &Os, updates: &mut impl Write, show_details: bool) -> Result<InvokeOutput> {
match self {
FsReadOperation::Line(fs_line) => fs_line.invoke(os, updates).await,
FsReadOperation::Directory(fs_directory) => fs_directory.invoke(os, updates).await,
FsReadOperation::Search(fs_search) => fs_search.invoke(os, updates).await,
FsReadOperation::Image(fs_image) => fs_image.invoke(updates).await,
FsReadOperation::Line(fs_line) => fs_line.invoke(os, updates, show_details).await,
FsReadOperation::Directory(fs_directory) => fs_directory.invoke(os, updates, show_details).await,
FsReadOperation::Search(fs_search) => fs_search.invoke(os, updates, show_details).await,
FsReadOperation::Image(fs_image) => fs_image.invoke(updates, show_details).await,
}
}
}
Expand Down Expand Up @@ -412,10 +413,10 @@ impl FsImage {
Ok(())
}

pub async fn invoke(&self, updates: &mut impl Write) -> Result<InvokeOutput> {
pub async fn invoke(&self, updates: &mut impl Write, show_details: bool) -> Result<InvokeOutput> {
let pre_processed_paths: Vec<String> = self.image_paths.iter().map(|path| pre_process(path)).collect();
let valid_images = handle_images_from_paths(updates, &pre_processed_paths);
super::queue_function_result("Successfully read image", updates, false, false)?;
super::queue_function_result("Successfully read image", updates, false, false, show_details)?;
Ok(InvokeOutput {
output: OutputKind::Images(valid_images),
})
Expand Down Expand Up @@ -498,7 +499,7 @@ impl FsLine {
}
}

pub async fn invoke(&self, os: &Os, updates: &mut impl Write) -> Result<InvokeOutput> {
pub async fn invoke(&self, os: &Os, updates: &mut impl Write, show_details: bool) -> Result<InvokeOutput> {
let path = sanitize_path_tool_arg(os, &self.path);
debug!(?path, "Reading");
let file_bytes = os.fs.read(&path).await?;
Expand Down Expand Up @@ -547,6 +548,7 @@ time. You tried to read {byte_count} bytes. Try executing with fewer lines speci
updates,
false,
false,
show_details,
)?;

Ok(InvokeOutput {
Expand Down Expand Up @@ -606,7 +608,7 @@ impl FsSearch {
Ok(())
}

pub async fn invoke(&self, os: &Os, updates: &mut impl Write) -> Result<InvokeOutput> {
pub async fn invoke(&self, os: &Os, updates: &mut impl Write, show_details: bool) -> Result<InvokeOutput> {
let file_path = sanitize_path_tool_arg(os, &self.path);
let pattern = &self.pattern;

Expand Down Expand Up @@ -653,6 +655,7 @@ impl FsSearch {
updates,
false,
false,
show_details,
)?;

Ok(InvokeOutput {
Expand Down Expand Up @@ -703,7 +706,7 @@ impl FsDirectory {
)?)
}

pub async fn invoke(&self, os: &Os, updates: &mut impl Write) -> Result<InvokeOutput> {
pub async fn invoke(&self, os: &Os, updates: &mut impl Write, show_details: bool) -> Result<InvokeOutput> {
let path = sanitize_path_tool_arg(os, &self.path);
let max_depth = self.depth();
debug!(?path, max_depth, "Reading directory at path with depth");
Expand Down Expand Up @@ -796,6 +799,7 @@ impl FsDirectory {
updates,
false,
false,
show_details,
)?;

Ok(InvokeOutput {
Expand Down
10 changes: 8 additions & 2 deletions crates/chat-cli/src/cli/chat/tools/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,11 @@ impl Tool {
stdout: &mut impl Write,
line_tracker: &mut HashMap<String, FileLineTracker>,
agents: &crate::cli::agent::Agents,
show_details: bool,
) -> Result<InvokeOutput> {
let active_agent = agents.get_active();
match self {
Tool::FsRead(fs_read) => fs_read.invoke(os, stdout).await,
Tool::FsRead(fs_read) => fs_read.invoke(os, stdout, show_details).await,
Tool::FsWrite(fs_write) => fs_write.invoke(os, stdout, line_tracker).await,
Tool::ExecuteCommand(execute_command) => execute_command.invoke(os, stdout).await,
Tool::UseAws(use_aws) => use_aws.invoke(os, stdout).await,
Expand Down Expand Up @@ -475,7 +476,12 @@ pub fn display_purpose(purpose: Option<&String>, updates: &mut impl Write) -> Re
/// * `updates` - The output to write to
/// * `is_error` - Whether this is an error message (changes formatting)
/// * `use_bullet` - Whether to use a bullet point instead of a tick/exclamation
pub fn queue_function_result(result: &str, updates: &mut impl Write, is_error: bool, use_bullet: bool) -> Result<()> {
pub fn queue_function_result(result: &str, updates: &mut impl Write, is_error: bool, use_bullet: bool, should_display: bool) -> Result<()> {
// Skip output if should_display is false
if !should_display {
return Ok(());
}

let lines = result.lines().collect::<Vec<_>>();

// Determine symbol and color
Expand Down
4 changes: 4 additions & 0 deletions crates/chat-cli/src/database/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ pub enum Setting {
EnabledDelegate,
#[strum(message = "Specify UI variant to use (string)")]
UiMode,
#[strum(message = "Show detailed tool execution information (boolean)")]
ChatToolOutputVerbose,
}

impl AsRef<str> for Setting {
Expand Down Expand Up @@ -133,6 +135,7 @@ impl AsRef<str> for Setting {
Self::EnabledContextUsageIndicator => "chat.enableContextUsageIndicator",
Self::EnabledDelegate => "chat.enableDelegate",
Self::UiMode => "chat.uiMode",
Self::ChatToolOutputVerbose => "chat.tool_output_verbose",
}
}
}
Expand Down Expand Up @@ -183,6 +186,7 @@ impl TryFrom<&str> for Setting {
"chat.enableCheckpoint" => Ok(Self::EnabledCheckpoint),
"chat.enableContextUsageIndicator" => Ok(Self::EnabledContextUsageIndicator),
"chat.uiMode" => Ok(Self::UiMode),
"chat.tool_output_verbose" => Ok(Self::ChatToolOutputVerbose),
_ => Err(DatabaseError::InvalidSetting(value.to_string())),
}
}
Expand Down