Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions src/cortex-tui/src/app/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,8 @@ impl AppState {
/// Update tool call result
pub fn update_tool_result(&mut self, id: &str, output: String, success: bool, summary: String) {
if let Some(call) = self.tool_calls.iter_mut().find(|c| c.id == id) {
// Clear live output when tool completes (replaced by result summary)
call.clear_live_output();
call.set_result(ToolResultDisplay {
output,
success,
Expand Down
10 changes: 8 additions & 2 deletions src/cortex-tui/src/runner/event_loop/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -669,8 +669,14 @@ impl EventLoop {
self.app_state.streaming.current_tool = None;
}

AppEvent::ToolProgress { name: _, status: _ } => {
// Tool progress updates are handled by stream controller
AppEvent::ToolProgress { name, status } => {
// Forward output to tool call's live_output buffer for real-time display
// Note: `name` here is actually the call_id from ExecCommandOutputDeltaEvent
for line in status.lines() {
if !line.is_empty() {
self.app_state.append_tool_output(&name, line.to_string());
}
}
}

AppEvent::ToolApproved(_) | AppEvent::ToolRejected(_) => {
Expand Down
57 changes: 53 additions & 4 deletions src/cortex-tui/src/views/tool_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,20 @@ pub fn format_tool_summary(name: &str, args: &Value) -> String {
format_first_arg(args)
}
"execute" | "bash" => {
if let Some(cmd) = args.get("command")
&& let Some(cmd_str) = cmd.as_str()
{
let truncated = truncate_str(cmd_str, 50);
if let Some(cmd) = args.get("command") {
// Handle both string and array formats
let cmd_str = if let Some(s) = cmd.as_str() {
s.to_string()
} else if let Some(arr) = cmd.as_array() {
// Join array elements with spaces
arr.iter()
.filter_map(|v| v.as_str())
.collect::<Vec<_>>()
.join(" ")
} else {
return format_first_arg(args);
};
let truncated = truncate_str(&cmd_str, 50);
return format!("$ {truncated}");
}
format_first_arg(args)
Expand Down Expand Up @@ -364,6 +374,37 @@ mod tests {
assert!(display.result.as_ref().unwrap().success);
}

#[test]
fn test_append_output_keeps_last_3_lines() {
let mut display =
ToolCallDisplay::new("test-id".to_string(), "execute".to_string(), json!({}), 0);

// Append 5 lines - should only keep last 3
display.append_output("line 1".to_string());
display.append_output("line 2".to_string());
display.append_output("line 3".to_string());
display.append_output("line 4".to_string());
display.append_output("line 5".to_string());

assert_eq!(display.live_output.len(), 3);
assert_eq!(display.live_output[0], "line 3");
assert_eq!(display.live_output[1], "line 4");
assert_eq!(display.live_output[2], "line 5");
}

#[test]
fn test_clear_live_output() {
let mut display =
ToolCallDisplay::new("test-id".to_string(), "execute".to_string(), json!({}), 0);

display.append_output("line 1".to_string());
display.append_output("line 2".to_string());
assert_eq!(display.live_output.len(), 2);

display.clear_live_output();
assert!(display.live_output.is_empty());
}

#[test]
fn test_format_tool_summary_read() {
let args = json!({"file_path": "/home/user/projects/myapp/src/main.rs"});
Expand All @@ -378,6 +419,14 @@ mod tests {
assert_eq!(summary, "$ cargo build --release");
}

#[test]
fn test_format_tool_summary_execute_array() {
// Execute tool receives command as array from LLM
let args = json!({"command": ["cargo", "build", "--release"]});
let summary = format_tool_summary("execute", &args);
assert_eq!(summary, "$ cargo build --release");
}

#[test]
fn test_format_tool_summary_websearch() {
let args = json!({"query": "rust async programming"});
Expand Down
Loading