commit 2be7bc400b3ad43c74e3edc83403be9253135989
parent 93a07519d0c371d514492134bd89d44496aa3df2
Author: William Casarin <jb55@jb55.com>
Date: Tue, 24 Feb 2026 18:38:29 -0800
dave: extract send_tool_result and complete_subagent to shared.rs
Tool result construction (format_tool_summary + ExecutedTool + send)
was repeated 4 times across claude.rs and codex.rs. Subagent
completion (stack pop + truncate + send) was duplicated in both.
Consolidate into shared helpers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
3 files changed, 75 insertions(+), 53 deletions(-)
diff --git a/crates/notedeck_dave/src/backend/claude.rs b/crates/notedeck_dave/src/backend/claude.rs
@@ -1,14 +1,12 @@
use crate::auto_accept::AutoAcceptRules;
use crate::backend::session_info::parse_session_info;
use crate::backend::shared::{self, SessionCommand, SessionHandle};
-use crate::backend::tool_summary::{
- extract_response_content, format_tool_summary, truncate_output,
-};
+use crate::backend::tool_summary::extract_response_content;
use crate::backend::traits::AiBackend;
use crate::file_update::FileUpdate;
use crate::messages::{
- CompactionInfo, DaveApiResponse, ExecutedTool, ParsedMarkdown, PendingPermission,
- PermissionRequest, PermissionResponse, SubagentInfo, SubagentStatus,
+ CompactionInfo, DaveApiResponse, ParsedMarkdown, PendingPermission, PermissionRequest,
+ PermissionResponse, SubagentInfo, SubagentStatus,
};
use crate::tools::Tool;
use crate::Message;
@@ -451,23 +449,13 @@ async fn session_actor(
// Check if this is a Task tool completion
if tool_name == "Task" {
- // Pop this subagent from the stack
- subagent_stack.retain(|id| id != tool_use_id);
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),
- });
+ shared::complete_subagent(tool_use_id, &result_text, &mut subagent_stack, &response_tx, &ctx);
}
- // Attach parent subagent context (top of stack)
- let parent_task_id = subagent_stack.last().cloned();
- let summary = format_tool_summary(&tool_name, &tool_input, &result_value);
let file_update = FileUpdate::from_tool_call(&tool_name, &tool_input);
- let tool_result = ExecutedTool { tool_name, summary, parent_task_id, file_update };
- let _ = response_tx.send(DaveApiResponse::ToolResult(tool_result));
- ctx.request_repaint();
+ shared::send_tool_result(&tool_name, &tool_input, &result_value, file_update, &subagent_stack, &response_tx, &ctx);
}
}
}
diff --git a/crates/notedeck_dave/src/backend/codex.rs b/crates/notedeck_dave/src/backend/codex.rs
@@ -3,13 +3,12 @@
use super::codex_protocol::*;
use super::shared::{self, SessionCommand, SessionHandle};
-use super::tool_summary::{format_tool_summary, truncate_output};
use crate::auto_accept::AutoAcceptRules;
use crate::backend::traits::AiBackend;
use crate::file_update::{FileUpdate, FileUpdateType};
use crate::messages::{
- CompactionInfo, DaveApiResponse, ExecutedTool, PendingPermission, PermissionRequest,
- PermissionResponse, SessionInfo, SubagentInfo, SubagentStatus,
+ CompactionInfo, DaveApiResponse, PendingPermission, PermissionRequest, PermissionResponse,
+ SessionInfo, SubagentInfo, SubagentStatus,
};
use crate::tools::Tool;
use crate::Message;
@@ -653,16 +652,17 @@ fn handle_codex_message(
"diff": diff_text,
});
let result_value = serde_json::json!({ "status": "ok" });
- let summary = format_tool_summary(tool_name, &tool_input, &result_value);
-
let file_update =
make_codex_file_update(path, tool_name, change_type, &diff_text);
- let _ = response_tx.send(DaveApiResponse::ToolResult(ExecutedTool {
- tool_name: tool_name.to_string(),
- summary,
- parent_task_id: subagent_stack.last().cloned(),
+ shared::send_tool_result(
+ tool_name,
+ &tool_input,
+ &result_value,
file_update,
- }));
+ subagent_stack,
+ response_tx,
+ ctx,
+ );
}
ctx.request_repaint();
}
@@ -776,16 +776,15 @@ fn handle_item_completed(
let tool_input = serde_json::json!({ "command": command });
let result_value = serde_json::json!({ "output": output, "exit_code": exit_code });
- let summary = format_tool_summary("Bash", &tool_input, &result_value);
- let parent_task_id = subagent_stack.last().cloned();
-
- let _ = response_tx.send(DaveApiResponse::ToolResult(ExecutedTool {
- tool_name: "Bash".to_string(),
- summary,
- parent_task_id,
- file_update: None,
- }));
- ctx.request_repaint();
+ shared::send_tool_result(
+ "Bash",
+ &tool_input,
+ &result_value,
+ None,
+ subagent_stack,
+ response_tx,
+ ctx,
+ );
}
"fileChange" => {
@@ -808,36 +807,30 @@ fn handle_item_completed(
"diff": diff,
});
let result_value = serde_json::json!({ "status": "ok" });
- let summary = format_tool_summary(tool_name, &tool_input, &result_value);
- let parent_task_id = subagent_stack.last().cloned();
-
let file_update = make_codex_file_update(
&file_path,
tool_name,
kind_str,
diff.as_deref().unwrap_or(""),
);
- let _ = response_tx.send(DaveApiResponse::ToolResult(ExecutedTool {
- tool_name: tool_name.to_string(),
- summary,
- parent_task_id,
+ shared::send_tool_result(
+ tool_name,
+ &tool_input,
+ &result_value,
file_update,
- }));
- ctx.request_repaint();
+ subagent_stack,
+ response_tx,
+ ctx,
+ );
}
"collabAgentToolCall" => {
if let Some(item_id) = &completed.item_id {
- subagent_stack.retain(|id| id != item_id);
let result_text = completed
.result
.clone()
.unwrap_or_else(|| "completed".to_string());
- let _ = response_tx.send(DaveApiResponse::SubagentCompleted {
- task_id: item_id.clone(),
- result: truncate_output(&result_text, 2000),
- });
- ctx.request_repaint();
+ shared::complete_subagent(item_id, &result_text, subagent_stack, response_tx, ctx);
}
}
diff --git a/crates/notedeck_dave/src/backend/shared.rs b/crates/notedeck_dave/src/backend/shared.rs
@@ -1,6 +1,8 @@
//! Shared utilities used by multiple AI backend implementations.
-use crate::messages::DaveApiResponse;
+use crate::backend::tool_summary::{format_tool_summary, truncate_output};
+use crate::file_update::FileUpdate;
+use crate::messages::{DaveApiResponse, ExecutedTool};
use crate::Message;
use claude_agent_sdk_rs::PermissionMode;
use std::sync::mpsc;
@@ -93,6 +95,45 @@ pub fn get_pending_user_messages(messages: &[Message]) -> String {
trailing.join("\n")
}
+/// Remove a completed subagent from the stack and notify the UI.
+pub fn complete_subagent(
+ task_id: &str,
+ result_text: &str,
+ subagent_stack: &mut Vec<String>,
+ response_tx: &mpsc::Sender<DaveApiResponse>,
+ ctx: &egui::Context,
+) {
+ subagent_stack.retain(|id| id != task_id);
+ let _ = response_tx.send(DaveApiResponse::SubagentCompleted {
+ task_id: task_id.to_string(),
+ result: truncate_output(result_text, 2000),
+ });
+ ctx.request_repaint();
+}
+
+/// Build an [`ExecutedTool`] from a completed tool call and send it
+/// to the UI along with a repaint request.
+pub fn send_tool_result(
+ tool_name: &str,
+ tool_input: &serde_json::Value,
+ result_value: &serde_json::Value,
+ file_update: Option<FileUpdate>,
+ subagent_stack: &[String],
+ response_tx: &mpsc::Sender<DaveApiResponse>,
+ ctx: &egui::Context,
+) {
+ let summary = format_tool_summary(tool_name, tool_input, result_value);
+ let parent_task_id = subagent_stack.last().cloned();
+ let tool_result = ExecutedTool {
+ tool_name: tool_name.to_string(),
+ summary,
+ parent_task_id,
+ file_update,
+ };
+ let _ = response_tx.send(DaveApiResponse::ToolResult(tool_result));
+ ctx.request_repaint();
+}
+
/// Decide which prompt to send based on whether we're resuming a
/// session and how many user messages exist.
///