commit f768a94d3168637e3aaf11dc2896df24892090d7
parent 8a454b8e63aa90ecd69e43264ec304ac735083db
Author: William Casarin <jb55@jb55.com>
Date: Wed, 25 Feb 2026 17:39:17 -0800
dave: extract process_events() match arms into standalone functions
Break down the 315-line process_events() method by extracting each
non-trivial match arm into a focused standalone function:
- handle_tool_calls() — executes tool calls and returns needs_send
- handle_permission_request() — builds events, stores sender, pushes to chat
- handle_tool_result() — invalidates git status, folds into subagent
- handle_subagent_spawned() — pushes to chat, records index
- handle_compaction_complete() — updates agentic state, advances compact-and-proceed
- handle_query_complete() — updates usage metrics
The match body is now ~40 lines of clean dispatch. Trivial arms
(Failed, Token, SubagentOutput, SubagentCompleted, CompactionStarted)
remain inline. Purely structural — no behavior changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
1 file changed, 196 insertions(+), 162 deletions(-)
diff --git a/crates/notedeck_dave/src/lib.rs b/crates/notedeck_dave/src/lib.rs
@@ -685,198 +685,48 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
DaveApiResponse::Failed(ref err) => {
session.chat.push(Message::Error(err.to_string()));
}
-
DaveApiResponse::Token(token) => {
session.append_token(&token);
}
-
DaveApiResponse::ToolCalls(toolcalls) => {
- tracing::info!("got tool calls: {:?}", toolcalls);
- session.chat.push(Message::ToolCalls(toolcalls.clone()));
-
- let txn = Transaction::new(app_ctx.ndb).unwrap();
- for call in &toolcalls {
- // execute toolcall
- match call.calls() {
- ToolCalls::PresentNotes(present) => {
- session.chat.push(Message::ToolResponse(ToolResponse::new(
- call.id().to_owned(),
- ToolResponses::PresentNotes(present.note_ids.len() as i32),
- )));
-
- needs_send.insert(session_id);
- }
-
- ToolCalls::Invalid(invalid) => {
- session.chat.push(Message::tool_error(
- call.id().to_string(),
- invalid.error.clone(),
- ));
-
- needs_send.insert(session_id);
- }
-
- ToolCalls::Query(search_call) => {
- let resp = search_call.execute(&txn, app_ctx.ndb);
- session.chat.push(Message::ToolResponse(ToolResponse::new(
- call.id().to_owned(),
- ToolResponses::Query(resp),
- )));
-
- needs_send.insert(session_id);
- }
- }
+ if handle_tool_calls(session, &toolcalls, app_ctx.ndb) {
+ needs_send.insert(session_id);
}
}
-
DaveApiResponse::PermissionRequest(pending) => {
- tracing::info!(
- "Permission request for tool '{}': {:?}",
- pending.request.tool_name,
- pending.request.tool_input
+ handle_permission_request(
+ session,
+ pending,
+ &secret_key,
+ app_ctx.ndb,
+ &mut events_to_publish,
);
-
- // Build and publish a proper permission request event
- // with perm-id, tool-name tags for remote clients
- if let Some(sk) = &secret_key {
- let event_session_id = session
- .agentic
- .as_ref()
- .and_then(|a| a.event_session_id().map(|s| s.to_string()));
-
- if let Some(sid) = event_session_id {
- match session_events::build_permission_request_event(
- &pending.request.id,
- &pending.request.tool_name,
- &pending.request.tool_input,
- &sid,
- sk,
- ) {
- Ok(evt) => {
- // PNS-wrap and ingest into local ndb
- pns_ingest(app_ctx.ndb, &evt.note_json, sk);
- // Store note_id for linking responses
- if let Some(agentic) = &mut session.agentic {
- agentic
- .permissions
- .request_note_ids
- .insert(pending.request.id, evt.note_id);
- }
- events_to_publish.push(evt);
- }
- Err(e) => {
- tracing::warn!(
- "failed to build permission request event: {}",
- e
- );
- }
- }
- }
- }
-
- // Store the response sender for later (agentic only)
- if let Some(agentic) = &mut session.agentic {
- agentic
- .permissions
- .pending
- .insert(pending.request.id, pending.response_tx);
- }
-
- // Add the request to chat for UI display
- session
- .chat
- .push(Message::PermissionRequest(pending.request));
}
-
DaveApiResponse::ToolResult(result) => {
- tracing::debug!("Tool result: {} - {}", result.tool_name, result.summary);
-
- // Invalidate git status after file-modifying tools.
- // tool_name is a String from the Claude SDK, no enum available.
- if matches!(result.tool_name.as_str(), "Bash" | "Write" | "Edit") {
- if let Some(agentic) = &mut session.agentic {
- agentic.git_status.invalidate();
- }
- }
- if let Some(result) = session.fold_tool_result(result) {
- session
- .chat
- .push(Message::ToolResponse(ToolResponse::executed_tool(result)));
- }
+ handle_tool_result(session, result);
}
-
DaveApiResponse::SessionInfo(info) => {
- tracing::debug!(
- "Session info: model={:?}, tools={}, agents={}",
- info.model,
- info.tools.len(),
- info.agents.len()
- );
handle_session_info(session, info, app_ctx.ndb);
}
-
DaveApiResponse::SubagentSpawned(subagent) => {
- tracing::debug!(
- "Subagent spawned: {} ({}) - {}",
- subagent.task_id,
- subagent.subagent_type,
- subagent.description
- );
- let task_id = subagent.task_id.clone();
- let idx = session.chat.len();
- session.chat.push(Message::Subagent(subagent));
- if let Some(agentic) = &mut session.agentic {
- agentic.subagent_indices.insert(task_id, idx);
- }
+ handle_subagent_spawned(session, subagent);
}
-
DaveApiResponse::SubagentOutput { task_id, output } => {
session.update_subagent_output(&task_id, &output);
}
-
DaveApiResponse::SubagentCompleted { task_id, result } => {
- tracing::debug!("Subagent completed: {}", task_id);
session.complete_subagent(&task_id, &result);
}
-
DaveApiResponse::CompactionStarted => {
- tracing::debug!("Compaction started for session {}", session_id);
if let Some(agentic) = &mut session.agentic {
agentic.is_compacting = true;
}
}
-
DaveApiResponse::CompactionComplete(info) => {
- tracing::debug!(
- "Compaction completed for session {}: pre_tokens={}",
- session_id,
- info.pre_tokens
- );
- if let Some(agentic) = &mut session.agentic {
- agentic.is_compacting = false;
- agentic.last_compaction = Some(info.clone());
-
- // Advance compact-and-proceed: compaction done,
- // proceed message will fire at stream-end.
- if agentic.compact_and_proceed
- == crate::session::CompactAndProceedState::WaitingForCompaction
- {
- agentic.compact_and_proceed =
- crate::session::CompactAndProceedState::ReadyToProceed;
- }
- }
- session.chat.push(Message::CompactionComplete(info));
+ handle_compaction_complete(session, session_id, info);
}
-
DaveApiResponse::QueryComplete(info) => {
- if let Some(agentic) = &mut session.agentic {
- agentic.usage.input_tokens = info.input_tokens;
- agentic.usage.output_tokens = info.output_tokens;
- agentic.usage.num_turns = info.num_turns;
- if let Some(cost) = info.cost_usd {
- agentic.usage.cost_usd = Some(cost);
- }
- }
+ handle_query_complete(session, info);
}
}
}
@@ -2827,6 +2677,190 @@ fn is_session_remote(hostname: &str, cwd: &str, local_hostname: &str) -> bool {
|| (hostname.is_empty() && !std::path::PathBuf::from(cwd).exists())
}
+/// Handle tool calls from the AI backend.
+///
+/// Pushes the tool calls to chat, executes each one, and pushes the
+/// responses. Returns `true` if any tool produced a response that
+/// needs to be sent back to the backend.
+fn handle_tool_calls(
+ session: &mut session::ChatSession,
+ toolcalls: &[ToolCall],
+ ndb: &nostrdb::Ndb,
+) -> bool {
+ tracing::info!("got tool calls: {:?}", toolcalls);
+ session.chat.push(Message::ToolCalls(toolcalls.to_vec()));
+
+ let txn = Transaction::new(ndb).unwrap();
+ let mut needs_send = false;
+
+ for call in toolcalls {
+ match call.calls() {
+ ToolCalls::PresentNotes(present) => {
+ session.chat.push(Message::ToolResponse(ToolResponse::new(
+ call.id().to_owned(),
+ ToolResponses::PresentNotes(present.note_ids.len() as i32),
+ )));
+ needs_send = true;
+ }
+ ToolCalls::Invalid(invalid) => {
+ session.chat.push(Message::tool_error(
+ call.id().to_string(),
+ invalid.error.clone(),
+ ));
+ needs_send = true;
+ }
+ ToolCalls::Query(search_call) => {
+ let resp = search_call.execute(&txn, ndb);
+ session.chat.push(Message::ToolResponse(ToolResponse::new(
+ call.id().to_owned(),
+ ToolResponses::Query(resp),
+ )));
+ needs_send = true;
+ }
+ }
+ }
+
+ needs_send
+}
+
+/// Handle a permission request from the AI backend.
+///
+/// Builds and publishes a permission request event for remote clients,
+/// stores the response sender for later, and adds the request to chat.
+fn handle_permission_request(
+ session: &mut session::ChatSession,
+ pending: messages::PendingPermission,
+ secret_key: &Option<[u8; 32]>,
+ ndb: &nostrdb::Ndb,
+ events_to_publish: &mut Vec<session_events::BuiltEvent>,
+) {
+ tracing::info!(
+ "Permission request for tool '{}': {:?}",
+ pending.request.tool_name,
+ pending.request.tool_input
+ );
+
+ // Build and publish a proper permission request event
+ // with perm-id, tool-name tags for remote clients
+ if let Some(sk) = secret_key {
+ let event_session_id = session
+ .agentic
+ .as_ref()
+ .and_then(|a| a.event_session_id().map(|s| s.to_string()));
+
+ if let Some(sid) = event_session_id {
+ match session_events::build_permission_request_event(
+ &pending.request.id,
+ &pending.request.tool_name,
+ &pending.request.tool_input,
+ &sid,
+ sk,
+ ) {
+ Ok(evt) => {
+ pns_ingest(ndb, &evt.note_json, sk);
+ if let Some(agentic) = &mut session.agentic {
+ agentic
+ .permissions
+ .request_note_ids
+ .insert(pending.request.id, evt.note_id);
+ }
+ events_to_publish.push(evt);
+ }
+ Err(e) => {
+ tracing::warn!("failed to build permission request event: {}", e);
+ }
+ }
+ }
+ }
+
+ // Store the response sender for later (agentic only)
+ if let Some(agentic) = &mut session.agentic {
+ agentic
+ .permissions
+ .pending
+ .insert(pending.request.id, pending.response_tx);
+ }
+
+ // Add the request to chat for UI display
+ session
+ .chat
+ .push(Message::PermissionRequest(pending.request));
+}
+
+/// Handle a tool result (execution metadata) from the AI backend.
+///
+/// Invalidates git status after file-modifying tools, then either folds
+/// the result into a subagent or pushes it as a standalone tool response.
+fn handle_tool_result(session: &mut session::ChatSession, result: ExecutedTool) {
+ tracing::debug!("Tool result: {} - {}", result.tool_name, result.summary);
+
+ if matches!(result.tool_name.as_str(), "Bash" | "Write" | "Edit") {
+ if let Some(agentic) = &mut session.agentic {
+ agentic.git_status.invalidate();
+ }
+ }
+ if let Some(result) = session.fold_tool_result(result) {
+ session
+ .chat
+ .push(Message::ToolResponse(ToolResponse::executed_tool(result)));
+ }
+}
+
+/// Handle a subagent spawn event from the AI backend.
+fn handle_subagent_spawned(session: &mut session::ChatSession, subagent: SubagentInfo) {
+ tracing::debug!(
+ "Subagent spawned: {} ({}) - {}",
+ subagent.task_id,
+ subagent.subagent_type,
+ subagent.description
+ );
+ let task_id = subagent.task_id.clone();
+ let idx = session.chat.len();
+ session.chat.push(Message::Subagent(subagent));
+ if let Some(agentic) = &mut session.agentic {
+ agentic.subagent_indices.insert(task_id, idx);
+ }
+}
+
+/// Handle compaction completion from the AI backend.
+///
+/// Updates agentic state, advances compact-and-proceed if waiting,
+/// and pushes the compaction info to chat.
+fn handle_compaction_complete(
+ session: &mut session::ChatSession,
+ session_id: SessionId,
+ info: messages::CompactionInfo,
+) {
+ tracing::debug!(
+ "Compaction completed for session {}: pre_tokens={}",
+ session_id,
+ info.pre_tokens
+ );
+ if let Some(agentic) = &mut session.agentic {
+ agentic.is_compacting = false;
+ agentic.last_compaction = Some(info.clone());
+
+ if agentic.compact_and_proceed
+ == crate::session::CompactAndProceedState::WaitingForCompaction
+ {
+ agentic.compact_and_proceed = crate::session::CompactAndProceedState::ReadyToProceed;
+ }
+ }
+ session.chat.push(Message::CompactionComplete(info));
+}
+
+/// Handle query completion (usage metrics) from the AI backend.
+fn handle_query_complete(session: &mut session::ChatSession, info: messages::UsageInfo) {
+ if let Some(agentic) = &mut session.agentic {
+ agentic.usage.input_tokens = info.input_tokens;
+ agentic.usage.output_tokens = info.output_tokens;
+ agentic.usage.num_turns = info.num_turns;
+ if let Some(cost) = info.cost_usd {
+ agentic.usage.cost_usd = Some(cost);
+ }
+ }
+}
+
/// Handle a SessionInfo response from the AI backend.
///
/// Sets up ndb subscriptions for permission responses and conversation events