commit 749a8e20ea39d51d764e4dff27e994869b17551e
parent 1b77a312146f8b1a89bdaa78f570ec8f2c2a18cf
Author: William Casarin <jb55@jb55.com>
Date: Tue, 10 Feb 2026 10:57:13 -0800
dave: fix tool result and subagent completion detection
The User message handler was checking for a nonexistent
tool_use_result key in the extra field. The SDK actually parses
tool results into UserMessage.content as ContentBlock::ToolResult
variants. Iterate over content blocks instead, fixing subagent
completions never being detected and tool result summaries never
being emitted.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
1 file changed, 34 insertions(+), 26 deletions(-)
diff --git a/crates/notedeck_dave/src/backend/claude.rs b/crates/notedeck_dave/src/backend/claude.rs
@@ -12,7 +12,8 @@ use crate::tools::Tool;
use crate::Message;
use claude_agent_sdk_rs::{
ClaudeAgentOptions, ClaudeClient, ContentBlock, Message as ClaudeMessage, PermissionMode,
- PermissionResult, PermissionResultAllow, PermissionResultDeny, ToolUseBlock, UserContentBlock,
+ PermissionResult, PermissionResultAllow, PermissionResultDeny, ToolResultContent, ToolUseBlock,
+ UserContentBlock,
};
use dashmap::DashMap;
use futures::future::BoxFuture;
@@ -25,6 +26,17 @@ use tokio::sync::mpsc as tokio_mpsc;
use tokio::sync::oneshot;
use uuid::Uuid;
+/// Convert a ToolResultContent to a serde_json::Value for use with tool summary formatting
+fn tool_result_content_to_value(content: &Option<ToolResultContent>) -> serde_json::Value {
+ match content {
+ Some(ToolResultContent::Text(s)) => serde_json::Value::String(s.clone()),
+ Some(ToolResultContent::Blocks(blocks)) => {
+ serde_json::Value::Array(blocks.iter().cloned().collect())
+ }
+ None => serde_json::Value::Null,
+ }
+}
+
/// Commands sent to a session's actor task
enum SessionCommand {
Query {
@@ -457,32 +469,28 @@ async fn session_actor(
stream_done = true;
}
ClaudeMessage::User(user_msg) => {
- if let Some(tool_use_result) = user_msg.extra.get("tool_use_result") {
- let tool_use_id = user_msg
- .extra
- .get("message")
- .and_then(|m| m.get("content"))
- .and_then(|c| c.as_array())
- .and_then(|arr| arr.first())
- .and_then(|item| item.get("tool_use_id"))
- .and_then(|id| id.as_str());
-
- if let Some(tool_use_id) = tool_use_id {
- if let Some((tool_name, tool_input)) = pending_tools.remove(tool_use_id) {
- // Check if this is a Task tool completion
- if tool_name == "Task" {
- let result_text = extract_response_content(tool_use_result)
- .unwrap_or_else(|| "completed".to_string());
- let _ = response_tx.send(DaveApiResponse::SubagentCompleted {
- task_id: tool_use_id.to_string(),
- result: truncate_output(&result_text, 2000),
- });
+ if let Some(content_blocks) = &user_msg.content {
+ for block in content_blocks {
+ if let ContentBlock::ToolResult(tool_result_block) = block {
+ let tool_use_id = &tool_result_block.tool_use_id;
+ if let Some((tool_name, tool_input)) = pending_tools.remove(tool_use_id) {
+ let result_value = tool_result_content_to_value(&tool_result_block.content);
+
+ // Check if this is a Task tool completion
+ if tool_name == "Task" {
+ let result_text = extract_response_content(&result_value)
+ .unwrap_or_else(|| "completed".to_string());
+ let _ = response_tx.send(DaveApiResponse::SubagentCompleted {
+ task_id: tool_use_id.to_string(),
+ result: truncate_output(&result_text, 2000),
+ });
+ }
+
+ let summary = format_tool_summary(&tool_name, &tool_input, &result_value);
+ let tool_result = ToolResult { tool_name, summary };
+ let _ = response_tx.send(DaveApiResponse::ToolResult(tool_result));
+ ctx.request_repaint();
}
-
- let summary = format_tool_summary(&tool_name, &tool_input, tool_use_result);
- let tool_result = ToolResult { tool_name, summary };
- let _ = response_tx.send(DaveApiResponse::ToolResult(tool_result));
- ctx.request_repaint();
}
}
}