From 7105205b04fd1e682d6e6c85628cbbeabf0d2c65 Mon Sep 17 00:00:00 2001 From: Brad Groux <3053586+BradGroux@users.noreply.github.com> Date: Sun, 16 Aug 2026 08:06:10 -0500 Subject: [PATCH 1/2] fix(cli): plumb include_aux so messages thread/get can see reactions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The relay already implements include_aux as a filter extension flag that walks a two-hop closure — reactions and deletions e-tagging the returned rows, then deletions of those. But buzz-cli never sends it, so messages get and messages thread cannot display reactions, edits, or deletions. Add --include-aux to both messages get and messages thread. When set, the filter JSON carries include_aux: true and the relay returns the aux events alongside the main results. No change to default behavior — the flag is opt-in. Closes #6005 Co-authored-by: Brad Groux Signed-off-by: Brad Groux Signed-off-by: dm-builder Signed-off-by: Brad Groux Signed-off-by: Brad Groux <3053586+BradGroux@users.noreply.github.com> --- crates/buzz-cli/src/commands/messages.rs | 13 +++++++++++++ crates/buzz-cli/src/lib.rs | 8 +++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/buzz-cli/src/commands/messages.rs b/crates/buzz-cli/src/commands/messages.rs index f80f928d316..4ef3daf0e4e 100644 --- a/crates/buzz-cli/src/commands/messages.rs +++ b/crates/buzz-cli/src/commands/messages.rs @@ -353,6 +353,7 @@ fn format_events(normalized: &str, format: &crate::OutputFormat) -> String { } } +#[allow(clippy::too_many_arguments)] pub async fn cmd_get_messages( client: &BuzzClient, channel_id: &str, @@ -360,6 +361,7 @@ pub async fn cmd_get_messages( before: Option, since: Option, kinds: Option<&str>, + include_aux: bool, format: &crate::OutputFormat, ) -> Result<(), CliError> { validate_uuid(channel_id)?; @@ -385,6 +387,9 @@ pub async fn cmd_get_messages( if let Some(s) = since { filter["since"] = serde_json::json!(s); } + if include_aux { + filter["include_aux"] = serde_json::json!(true); + } let resp = client.query(&filter).await?; let mut events: Vec = serde_json::from_str(&resp).unwrap_or_default(); @@ -424,6 +429,7 @@ pub async fn cmd_get_thread( expected_root_id: Option<&str>, limit: Option, depth_limit: Option, + include_aux: bool, format: &crate::OutputFormat, ) -> Result<(), CliError> { let expected_channel_id = parse_uuid(channel_id)?; @@ -446,6 +452,9 @@ pub async fn cmd_get_thread( if let Some(d) = depth_limit { reply_filter["depth_limit"] = serde_json::json!(d); } + if include_aux { + reply_filter["include_aux"] = serde_json::json!(true); + } let root_filter = serde_json::json!({ "ids": [root_event_id.as_str()], "#h": [channel_id], @@ -1015,6 +1024,7 @@ pub async fn dispatch( before, since, kinds, + include_aux, } => { cmd_get_messages( client, @@ -1023,6 +1033,7 @@ pub async fn dispatch( before, since, kinds.as_deref(), + include_aux, format, ) .await @@ -1033,6 +1044,7 @@ pub async fn dispatch( link, limit, depth_limit, + include_aux, } => { let (channel, event, expected_root) = match link { @@ -1055,6 +1067,7 @@ pub async fn dispatch( expected_root.as_deref(), limit, depth_limit, + include_aux, format, ) .await diff --git a/crates/buzz-cli/src/lib.rs b/crates/buzz-cli/src/lib.rs index 3f2bea73979..fd7c01c95dd 100644 --- a/crates/buzz-cli/src/lib.rs +++ b/crates/buzz-cli/src/lib.rs @@ -464,7 +464,7 @@ pub enum MessagesCmd { }, /// Retrieve messages from a channel #[command( - after_help = "Examples:\n buzz messages get --channel \n buzz messages get --channel --limit 50 --kinds 1,1984" + after_help = "Examples:\n buzz messages get --channel \n buzz messages get --channel --limit 50 --kinds 1,1984\n buzz messages get --channel --include-aux" )] Get { /// Channel UUID @@ -482,6 +482,9 @@ pub enum MessagesCmd { /// Comma-separated event kinds to filter (e.g. 1,1984) #[arg(long)] kinds: Option, + /// Include reactions, deletions, and edits attached to returned messages + #[arg(long)] + include_aux: bool, }, /// Get the containing thread for a message or Buzz message link #[command( @@ -503,6 +506,9 @@ pub enum MessagesCmd { /// Maximum reply nesting depth to include #[arg(long)] depth_limit: Option, + /// Include reactions, deletions, and edits attached to returned messages + #[arg(long)] + include_aux: bool, }, /// Full-text search across messages #[command( From 6cea378c321b8d205897d7f32f0532def900afc5 Mon Sep 17 00:00:00 2001 From: Brad Groux <3053586+BradGroux@users.noreply.github.com> Date: Tue, 25 Aug 2026 14:20:11 -0500 Subject: [PATCH 2/2] Wire include_aux into the catch-all query path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The relay only evaluated include_aux in the channel-window path (top_level: true) and the depth-limited thread path. The catch-all path, used by messages get and the root-event ids filter in messages thread, did not read the extension — so --include-aux returned the same event set without reactions, deletions, or edits on those commands. Use the existing build_aux_query / query_all_pages / AuxReader infrastructure (added in a recent main refactor) to run the same two-hop reaction/deletion/edit closure via routed reads. Wire it into the catch-all Phase 3 post-processing loop, mirroring the thread path's structure. The thread path already had include_aux support from a recent main update, so only the catch-all path needed this addition. Co-authored-by: Brad Groux Signed-off-by: Brad Groux Signed-off-by: Brad Groux <3053586+BradGroux@users.noreply.github.com> --- crates/buzz-relay/src/api/bridge.rs | 50 +++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/crates/buzz-relay/src/api/bridge.rs b/crates/buzz-relay/src/api/bridge.rs index 37c549610de..a1310c7bfba 100644 --- a/crates/buzz-relay/src/api/bridge.rs +++ b/crates/buzz-relay/src/api/bridge.rs @@ -1509,6 +1509,56 @@ async fn query_events_authed( return Err(internal_error(&format!("query error: {e}"))); } } + + // Aux closure for the catch-all path: include reactions, deletions, + // and edits targeting the returned events, plus deletions targeting + // those aux events. This covers `messages get` (no top_level, no + // depth_limit) and the root-event filter in `messages thread` (an + // `ids` filter that falls through to catch-all). + if extension_flag(&raw_filters[idx], "include_aux") { + let result_ids: Vec = events + .iter() + .filter_map(|e| e.get("id").and_then(|v| v.as_str()).map(String::from)) + .collect(); + if !result_ids.is_empty() { + let mut seen_aux = std::collections::HashSet::new(); + let mut hop_ids = result_ids; + for hop_kinds in [&WINDOW_AUX_KINDS[..], &WINDOW_AUX_DELETE_KINDS[..]] { + let aux_query = build_aux_query( + tenant.community(), + std::mem::take(&mut hop_ids), + hop_kinds, + ); + let aux_events = query_all_pages( + aux_query, + AUX_PAGE_LIMIT, + &mut AuxReader::Routed(&state.db, "bridge_catchall_aux"), + ) + .await + .map_err(|e| { + internal_error(&format!("catch-all aux query error: {e}")) + })?; + for se in aux_events { + if !seen_aux.insert(se.event.id) + || !event_in_accessible_channel(&se, &accessible_channels) + || !crate::handlers::req::event_visible_to_reader( + &se.event, + &pubkey_bytes, + ) + { + continue; + } + hop_ids.push(se.event.id.to_hex()); + if let Ok(value) = serde_json::to_value(&se.event) { + events.push(value); + } + } + if hop_ids.is_empty() { + break; + } + } + } + } } Ok(Json(Value::Array(events)))