Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/buzz-core/src/kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ pub const KIND_PAIRING: u32 = 24134;
pub const KIND_TYPING_INDICATOR: u32 = 20002;
/// Ephemeral: owner-scoped encrypted agent observer telemetry and control frame.
pub const KIND_AGENT_OBSERVER_FRAME: u32 = 24200;
/// Ephemeral: huddle emoji reaction burst. Channel-scoped to the ephemeral
/// huddle channel with an `h` tag; never stored in the timeline.
pub const KIND_HUDDLE_REACTION: u32 = 24810;
/// Ephemeral: mesh status report (desktop → relay). A relay member reports its
/// current mesh serve availability + EndpointAddr(s) so the relay can project a
/// sanitized, relay-signed kind:30621 discovery note keyed per reporter. Tagged
Expand Down Expand Up @@ -395,6 +398,7 @@ pub const ALL_KINDS: &[u32] = &[
KIND_NIP29_GROUP_ROLES,
KIND_PRESENCE_UPDATE,
KIND_TYPING_INDICATOR,
KIND_HUDDLE_REACTION,
KIND_MESH_STATUS_REPORT,
KIND_MESH_CONNECT_REQUEST,
KIND_MESH_CALL_ME_NOW,
Expand Down
33 changes: 33 additions & 0 deletions desktop/src-tauri/src/huddle/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ async fn emit_end_and_archive(
}
}
}
remove_huddle_agents(ephemeral_channel_id, state).await;
if !ephemeral_channel_id.is_empty() {
if let Ok(uuid) = parse_channel_uuid(ephemeral_channel_id) {
if let Ok(archive_builder) = events::build_archive(uuid) {
Expand All @@ -438,6 +439,38 @@ async fn emit_end_and_archive(
}
}

/// Best-effort agent removal before archiving an ended huddle.
///
/// Archive should make the ephemeral channel unavailable on its own, but removing
/// bot-role members first prevents an agent-only huddle from lingering if the
/// archive publish is delayed or rejected.
async fn remove_huddle_agents(ephemeral_channel_id: &str, state: &AppState) {
if ephemeral_channel_id.is_empty() {
return;
}
let Ok(eph_uuid) = parse_channel_uuid(ephemeral_channel_id) else {
return;
};

let agent_pubkeys = match fetch_channel_members(ephemeral_channel_id, Some("bot"), state).await
{
Ok(pubkeys) => pubkeys,
Err(e) => {
eprintln!("buzz-desktop: fetch huddle agents for cleanup failed: {e}");
return;
}
};

for pubkey in agent_pubkeys {
let Ok(remove_builder) = events::build_remove_member(eph_uuid, &pubkey) else {
continue;
};
if let Err(e) = submit_event(remove_builder, state).await {
eprintln!("buzz-desktop: remove huddle agent {pubkey} failed: {e}");
}
}
}

/// Leave the current huddle.
///
/// Steps:
Expand Down
13 changes: 7 additions & 6 deletions desktop/src-tauri/src/huddle/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@ use super::{stt, tts};

/// Voice input mode: push-to-talk (PTT) or voice-activity detection (VAD).
///
/// PTT (default): mic is gated by a global shortcut (Ctrl+Space). Pressing the
/// key sets `ptt_active` and immediately cancels any playing TTS. Releasing
/// the key (after a 200 ms delay) stops mic capture and flushes the utterance.
/// PTT: mic is gated by a global shortcut (Ctrl+Space). Pressing the key sets
/// `ptt_active` and immediately cancels any playing TTS. Releasing the key
/// (after a 200 ms delay) stops mic capture and flushes the utterance.
///
/// VAD: the earshot VAD runs continuously and speech is accumulated whenever
/// the probability exceeds the threshold. Barge-in is enabled in this mode.
/// VAD (default): the earshot VAD runs continuously and speech is accumulated
/// whenever the probability exceeds the threshold. Barge-in is enabled in this
/// mode.
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum VoiceInputMode {
#[default]
PushToTalk,
#[default]
VoiceActivity,
}

Expand Down
Loading