Skip to content

Commit eb7db1f

Browse files
committed
fix(tui): display subagent todos and prevent HTTP 413 payload errors
This commit addresses two issues when running tasks via the Task tool: 1. Todo list display: When a subagent calls TodoWrite, the todos are now parsed and forwarded to the UI via a new ToolEvent::TodoUpdated event. The SubagentTaskDisplay in the TUI is then updated to show the todos with their status (pending, in_progress, completed). 2. HTTP 413 Payload Too Large: Tool outputs from subagent tool calls are now truncated to 32KB to prevent the cumulative context from exceeding backend payload limits. This is especially important for tools like Grep or Read that can return large outputs. Changes: - Add ToolEvent::TodoUpdated variant to events.rs - Detect TodoWrite calls in spawn_subagent and send todo updates to UI - Truncate tool outputs > 32KB with a clear truncation message - Handle TodoUpdated event in handle_tool_event to update app_state
1 parent 18de66e commit eb7db1f

File tree

2 files changed

+88
-1
lines changed

2 files changed

+88
-1
lines changed

cortex-tui/src/events.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ pub enum ToolEvent {
5959
error: String,
6060
duration: std::time::Duration,
6161
},
62+
/// Subagent todo list was updated (from TodoWrite tool).
63+
/// Used to update the UI's SubagentTaskDisplay with real-time todo progress.
64+
TodoUpdated {
65+
/// Session ID of the subagent (e.g., "subagent_<tool_call_id>")
66+
session_id: String,
67+
/// Todo items: (content, status) where status is "pending", "in_progress", or "completed"
68+
todos: Vec<(String, String)>,
69+
},
6270
}
6371

6472
/// Events from subagent executions.

cortex-tui/src/runner/event_loop.rs

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -753,9 +753,38 @@ impl EventLoop {
753753
)
754754
.collect();
755755

756+
// Maximum size for tool result output to prevent HTTP 413 Payload Too Large
757+
const MAX_TOOL_OUTPUT_SIZE: usize = 32_000; // 32KB per tool result
758+
756759
for (tc_id, tc_name, tc_args) in &iteration_tool_calls {
757760
tracing::info!("Subagent executing tool: {} ({})", tc_name, tc_id);
758761

762+
// If this is TodoWrite, parse and send todo updates to UI
763+
if tc_name == "TodoWrite" {
764+
if let Some(todos_arr) = tc_args.get("todos").and_then(|v| v.as_array()) {
765+
let todos: Vec<(String, String)> = todos_arr
766+
.iter()
767+
.filter_map(|t| {
768+
let content = t.get("content").and_then(|v| v.as_str())?;
769+
let status = t
770+
.get("status")
771+
.and_then(|v| v.as_str())
772+
.unwrap_or("pending");
773+
Some((content.to_string(), status.to_string()))
774+
})
775+
.collect();
776+
777+
if !todos.is_empty() {
778+
let _ = tool_tx
779+
.send(ToolEvent::TodoUpdated {
780+
session_id: format!("subagent_{}", id),
781+
todos,
782+
})
783+
.await;
784+
}
785+
}
786+
}
787+
759788
let result = registry.execute(tc_name, tc_args.clone()).await;
760789
match result {
761790
Ok(tool_result) => {
@@ -765,7 +794,20 @@ impl EventLoop {
765794
"failed"
766795
};
767796
tool_calls_executed.push(format!("{}: {}", tc_name, status));
768-
tool_results.push((tc_id.clone(), tool_result.output));
797+
798+
// Truncate large tool outputs to prevent payload too large errors
799+
let output = if tool_result.output.len() > MAX_TOOL_OUTPUT_SIZE {
800+
let truncated = &tool_result.output[..MAX_TOOL_OUTPUT_SIZE];
801+
format!(
802+
"{}...\n\n[Output truncated: {} bytes total, showing first {} bytes]",
803+
truncated,
804+
tool_result.output.len(),
805+
MAX_TOOL_OUTPUT_SIZE
806+
)
807+
} else {
808+
tool_result.output
809+
};
810+
tool_results.push((tc_id.clone(), output));
769811
}
770812
Err(e) => {
771813
let error_msg = format!("Error executing {}: {}", tc_name, e);
@@ -3384,6 +3426,43 @@ impl EventLoop {
33843426
}
33853427
}
33863428
}
3429+
3430+
ToolEvent::TodoUpdated { session_id, todos } => {
3431+
// Update the subagent's todo list in the UI
3432+
use crate::app::{SubagentTodoItem, SubagentTodoStatus};
3433+
3434+
tracing::debug!(
3435+
"Subagent todo list updated: {} ({} items)",
3436+
session_id,
3437+
todos.len()
3438+
);
3439+
3440+
self.app_state.update_subagent(&session_id, |task| {
3441+
task.todos = todos
3442+
.iter()
3443+
.map(|(content, status)| {
3444+
let status = match status.as_str() {
3445+
"in_progress" => SubagentTodoStatus::InProgress,
3446+
"completed" => SubagentTodoStatus::Completed,
3447+
_ => SubagentTodoStatus::Pending,
3448+
};
3449+
SubagentTodoItem {
3450+
content: content.clone(),
3451+
status,
3452+
}
3453+
})
3454+
.collect();
3455+
3456+
// Update activity based on in-progress item
3457+
if let Some(in_progress) = task
3458+
.todos
3459+
.iter()
3460+
.find(|t| matches!(t.status, SubagentTodoStatus::InProgress))
3461+
{
3462+
task.current_activity = in_progress.content.clone();
3463+
}
3464+
});
3465+
}
33873466
}
33883467
}
33893468

0 commit comments

Comments
 (0)