notedeck

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

commit e6c9c5427f76aefa505e5ff68a20b9b4ed1e42ba
parent 44233ecf9f1df49d19b76b95f2429b3b98183e34
Author: William Casarin <jb55@jb55.com>
Date:   Wed, 18 Feb 2026 10:09:33 -0800

add tool-name tag to tool_call/tool_result nostr events

Tool result messages from nostr notes were showing "tool" as the tool
name instead of the actual name (e.g. "Bash", "Read") because the
tool-name tag was never written when building events. The session_loader
was already reading this tag but it always fell back to "tool".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Diffstat:
Mcrates/notedeck_dave/src/lib.rs | 13++++++++++---
Mcrates/notedeck_dave/src/session_events.rs | 56++++++++++++++++++++++++++++++++++++++++++++------------
2 files changed, 54 insertions(+), 15 deletions(-)

diff --git a/crates/notedeck_dave/src/lib.rs b/crates/notedeck_dave/src/lib.rs @@ -197,6 +197,7 @@ fn ingest_live_event( content: &str, role: &str, tool_id: Option<&str>, + tool_name: Option<&str>, ) -> Option<session_events::BuiltEvent> { let agentic = session.agentic.as_mut()?; let session_id = agentic.event_session_id().map(|s| s.to_string())?; @@ -208,6 +209,7 @@ fn ingest_live_event( &session_id, cwd, tool_id, + tool_name, &mut agentic.live_threading, secret_key, ) { @@ -411,7 +413,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr DaveApiResponse::Failed(ref err) => { if let Some(sk) = &secret_key { if let Some(evt) = - ingest_live_event(session, app_ctx.ndb, sk, err, "error", None) + ingest_live_event(session, app_ctx.ndb, sk, err, "error", None, None) { events_to_publish.push(evt); } @@ -538,6 +540,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr &content, "tool_result", None, + Some(result.tool_name.as_str()), ) { events_to_publish.push(evt); } @@ -683,6 +686,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr &text, "assistant", None, + None, ) { events_to_publish.push(evt); } @@ -1599,10 +1603,13 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr } else { content.to_string() }; + let tool_name = session_events::get_tag_value(note, "tool-name") + .unwrap_or("tool") + .to_string(); session .chat .push(Message::ToolResult(crate::messages::ToolResult { - tool_name: "tool".to_string(), + tool_name, summary, })); } @@ -1827,7 +1834,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr // Generate live event for user message if let Some(sk) = secret_key_bytes(app_ctx.accounts.get_selected_account().keypair()) { if let Some(evt) = - ingest_live_event(session, app_ctx.ndb, &sk, &user_text, "user", None) + ingest_live_event(session, app_ctx.ndb, &sk, &user_text, "user", None, None) { self.pending_relay_events.push(evt); } diff --git a/crates/notedeck_dave/src/session_events.rs b/crates/notedeck_dave/src/session_events.rs @@ -258,6 +258,19 @@ pub fn build_events( ContentBlock::ToolResult { tool_use_id, .. } => Some(*tool_use_id), _ => None, }; + let tool_name = match block { + ContentBlock::ToolUse { name, .. } => Some(*name), + ContentBlock::ToolResult { tool_use_id, .. } => { + // Look up tool name from a prior ToolUse block with matching id + blocks.iter().find_map(|b| match b { + ContentBlock::ToolUse { id, name, .. } if *id == *tool_use_id => { + Some(*name) + } + _ => None, + }) + } + _ => None, + }; let event = build_single_event( Some(line), @@ -266,6 +279,7 @@ pub fn build_events( "claude-code", Some((i, total)), tool_id, + tool_name, session_id.as_deref(), None, timestamp, @@ -281,19 +295,26 @@ pub fn build_events( let content = session_jsonl::extract_display_content(line); let role = line.role().unwrap_or("unknown"); - // Extract tool_id from single-block messages - let tool_id = msg.as_ref().and_then(|m| { - let blocks = m.content_blocks(); - if blocks.len() == 1 { - match &blocks[0] { - ContentBlock::ToolUse { id, .. } => Some(id.to_string()), - ContentBlock::ToolResult { tool_use_id, .. } => Some(tool_use_id.to_string()), - _ => None, + // Extract tool_id and tool_name from single-block messages + let (tool_id, tool_name) = msg + .as_ref() + .and_then(|m| { + let blocks = m.content_blocks(); + if blocks.len() == 1 { + match &blocks[0] { + ContentBlock::ToolUse { id, name, .. } => { + Some((id.to_string(), Some(name.to_string()))) + } + ContentBlock::ToolResult { tool_use_id, .. } => { + Some((tool_use_id.to_string(), None)) + } + _ => None, + } + } else { + None } - } else { - None - } - }); + }) + .map_or((None, None), |(id, name)| (Some(id), name)); let event = build_single_event( Some(line), @@ -302,6 +323,7 @@ pub fn build_events( "claude-code", None, tool_id.as_deref(), + tool_name.as_deref(), session_id.as_deref(), None, timestamp, @@ -391,6 +413,8 @@ fn build_source_data_event( /// assistant message. /// /// `tool_id`: The tool use/result ID for tool_call and tool_result events. +/// +/// `tool_name`: The tool name (e.g. "Bash", "Read") for tool_call and tool_result events. #[allow(clippy::too_many_arguments)] fn build_single_event( line: Option<&JsonlLine>, @@ -399,6 +423,7 @@ fn build_single_event( source: &str, split_index: Option<(usize, usize)>, tool_id: Option<&str>, + tool_name: Option<&str>, session_id: Option<&str>, cwd: Option<&str>, timestamp: Option<u64>, @@ -475,6 +500,11 @@ fn build_single_event( builder = builder.start_tag().tag_str("tool-id").tag_str(tid); } + // -- Tool name tag -- + if let Some(tn) = tool_name { + builder = builder.start_tag().tag_str("tool-name").tag_str(tn); + } + // -- Discoverability -- builder = builder.start_tag().tag_str("t").tag_str("ai-conversation"); @@ -493,6 +523,7 @@ pub fn build_live_event( session_id: &str, cwd: Option<&str>, tool_id: Option<&str>, + tool_name: Option<&str>, threading: &mut ThreadingState, secret_key: &[u8; 32], ) -> Result<BuiltEvent, EventBuildError> { @@ -503,6 +534,7 @@ pub fn build_live_event( "notedeck-dave", None, tool_id, + tool_name, Some(session_id), cwd, Some(now_secs()),