notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

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:
Mcrates/notedeck_dave/src/backend/claude.rs | 22+++++-----------------
Mcrates/notedeck_dave/src/backend/codex.rs | 63++++++++++++++++++++++++++++-----------------------------------
Mcrates/notedeck_dave/src/backend/shared.rs | 43++++++++++++++++++++++++++++++++++++++++++-
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. ///