From ab8f1862c2ca0a3f97f7599d69625d521337cba9 Mon Sep 17 00:00:00 2001 From: QuicksilverSlick Date: Sun, 30 Aug 2026 13:37:16 -0500 Subject: [PATCH 01/10] fix(buzz-acp): tell a refused author instead of dropping them silently MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An inbound event from an author outside the gate was dropped with a tracing::debug! and a `continue`. Nothing was posted, no reaction was added, and debug is off in practice — so the sender got silence and the owner never learned anyone had tried to reach the agent. RespondTo::default() is OwnerOnly, so this is the first thing a newly invited collaborator hits: their opening message vanishes, and both sides believe the other is unresponsive. The refusal now: - logs at warn! rather than debug! — a person who got no answer is not a debug-level event; - posts one in-channel notice per (channel, author), anchored to the sender's thread, saying what happened, that their message is not lost, and what has to change for it to be picked up; - p-tags the owner in channels, so they learn through the normal mention path, in the project the request arrived in. The agent never opens a DM: keeping the exchange with its project preserves the context, and an inbox of agent DMs is an inbox people stop reading. One notice per person per channel, not per event: silence strands the sender, but replying every time lets a noisy author use the agent as a flooder. The memo is bounded, since anyone can mint a pubkey and post — at the cap it clears rather than evicting, because the cost of forgetting is one repeat notice to someone already told. In a DM the notice carries no owner mention and does not name them: the sender is an unknown party and the owner's pubkey is not theirs to learn. The existing DM hardening is untouched — this changes only whether a refusal is visible, never who is allowed to steer. Co-Authored-By: Claude Opus 5 --- crates/buzz-acp/src/lib.rs | 209 +++++++++++++++++++++++++++++++++++- crates/buzz-acp/src/pool.rs | 58 ++++++++++ 2 files changed, 264 insertions(+), 3 deletions(-) diff --git a/crates/buzz-acp/src/lib.rs b/crates/buzz-acp/src/lib.rs index 25c6e549052..0f680fd9c0d 100644 --- a/crates/buzz-acp/src/lib.rs +++ b/crates/buzz-acp/src/lib.rs @@ -2265,6 +2265,14 @@ async fn tokio_main() -> Result<()> { let mut typing_channels: HashMap = HashMap::new(); let mut presence_task: Option> = None; + // (channel, author) pairs already told they are outside the author gate. + // One notice per person per channel: silence strands the sender, but a + // reply per refused event lets a noisy author turn the agent into a + // flooder. Bounded because this process is long-lived and the key space is + // attacker-controlled — on overflow we clear and allow one more notice per + // pair rather than growing without limit. + let mut gate_notified: HashSet<(Uuid, String)> = HashSet::new(); + // Independent of pool readiness: a never-mentioned lazy agent must still // self-terminate. The watch interval is capped so small configured bounds // remain reasonably precise without waking long-lived agents frequently. @@ -2884,13 +2892,53 @@ async fn tokio_main() -> Result<()> { ) .await; if !allowed { - tracing::debug!( + // warn, not debug: a refused request is a + // person who got no answer. At debug level + // this is invisible in practice, so the + // sender is stranded and the owner never + // learns anyone tried. + tracing::warn!( channel_id = %buzz_event.channel_id, - author = %buzz_event.event.pubkey.to_hex(), + author = %author, mode = %config.respond_to, is_dm, - "inbound author gate — dropping event" + "inbound author gate — refusing event" ); + + if remember_gate_notice( + &mut gate_notified, + (buzz_event.channel_id, author.clone()), + ) { + // Mention the owner in channels so they + // learn through the normal mention + // path, in the project where the + // request arrived. Never in a DM: that + // would disclose the owner's pubkey to + // a stranger. + let mentions: Vec = if is_dm { + Vec::new() + } else { + owner_cache + .get() + .map(|o| vec![o.to_string()]) + .unwrap_or_default() + }; + let content = author_gate_notice_text(is_dm); + let thread_tags = + queue::parse_thread_tags(&buzz_event.event); + let rest = ctx.rest_client.clone(); + let channel_id = buzz_event.channel_id; + tokio::spawn(async move { + pool::post_gate_notice( + &rest, + channel_id, + &thread_tags, + &content, + &mentions, + ) + .await; + }); + } continue; } } @@ -3854,6 +3902,61 @@ fn is_auth_error(error: &acp::AcpError) -> bool { /// /// Shared by the hard-cap immediate dead-letter path and the retries-exhausted /// dead-letter path so neither duplicates the tokio::spawn block. +/// Upper bound on remembered (channel, author) gate refusals. +/// +/// The key space is attacker-controlled — anyone can mint a pubkey and post — +/// so this cannot grow unbounded in a long-lived process. At the cap the memo +/// is cleared rather than evicted per-entry: the cost of forgetting is one +/// extra notice to a person who has already been told, which is far cheaper +/// than the bookkeeping to avoid it. +const MAX_GATE_NOTIFIED: usize = 4_096; + +/// Record that `key` has been told it sits outside the author gate, and report +/// whether this is the first time (so the caller should post a notice). +/// +/// Clearing at the cap, rather than evicting one entry, is deliberate: the cost +/// of forgetting is one repeat notice to someone already told, which is far +/// cheaper than the bookkeeping an LRU would need on a path that exists only to +/// suppress duplicates. +fn remember_gate_notice(seen: &mut HashSet<(Uuid, String)>, key: (Uuid, String)) -> bool { + if seen.contains(&key) { + return false; + } + if seen.len() >= MAX_GATE_NOTIFIED { + tracing::warn!( + tracked = seen.len(), + "author gate notice memo full — clearing" + ); + seen.clear(); + } + seen.insert(key); + true +} + +/// What someone outside the author gate is told. +/// +/// Says what happened, what it means for them, and what happens next — without +/// naming the mechanism. "I'm not set up to take requests from you" is +/// actionable; "inbound author gate rejected event" is not. It never asks them +/// to resend: the message is already in the channel, and re-typing it will not +/// change the outcome. +fn author_gate_notice_text(is_dm: bool) -> String { + if is_dm { + // No project channel to point at, and no owner mention — naming the + // owner here would hand a stranger their pubkey. + "I'm not currently set up to take requests from you, so I can't act on this. \ + Your message hasn't gone anywhere and you don't need to send it again. \ + If you're expecting access, ask whoever runs this workspace to add you." + .to_string() + } else { + "I saw your message but I'm not currently set up to take requests from you, \ + so I can't start on it. Your message is safe in this channel and nothing \ + needs re-typing. I've flagged this for the owner of this project — once \ + they add you, send it again and I'll pick it up." + .to_string() + } +} + fn spawn_failure_notice( rest_client: Option<&relay::RestClient>, batch: &FlushBatch, @@ -5508,6 +5611,106 @@ mod author_gate_tests { } } + // ── Author gate notice ──────────────────────────────────────────────── + // + // A refused author used to be dropped at debug level with no reply, so the + // sender was stranded and the owner never learned anyone had tried. These + // lock down the properties that make the refusal visible instead. + + #[test] + fn test_gate_notice_says_what_happened_and_what_next() { + for is_dm in [false, true] { + let text = author_gate_notice_text(is_dm); + assert!( + text.contains("can't"), + "notice must state that the request will not be acted on (is_dm={is_dm})" + ); + assert!( + text.contains("send it again") || text.contains("add you"), + "notice must tell the sender what happens next (is_dm={is_dm})" + ); + assert!( + !text.contains("author gate") + && !text.contains("respond_to") + && !text.contains("pubkey"), + "notice must name the state, not the mechanism (is_dm={is_dm})" + ); + } + } + + #[test] + fn test_gate_notice_never_asks_for_a_resend_of_the_same_message() { + // Re-sending cannot change the outcome — asking for it is the pattern + // that trains people to repeat themselves into silence. + let channel = author_gate_notice_text(false); + assert!( + channel.contains("nothing") && channel.contains("re-typing"), + "channel notice must reassure that the message is not lost: {channel}" + ); + let dm = author_gate_notice_text(true); + assert!( + dm.contains("don't need to send it again"), + "DM notice must reassure that the message is not lost: {dm}" + ); + } + + #[test] + fn test_gate_notice_does_not_leak_owner_in_dm() { + // In a channel the owner is p-tagged so they learn someone tried. In a + // DM there is no such mention, and the text must not compensate by + // naming them — the sender is an unknown party. + let dm = author_gate_notice_text(true); + assert!( + !dm.contains("owner of this project"), + "DM notice must not point an unknown sender at the owner: {dm}" + ); + let channel = author_gate_notice_text(false); + assert!( + channel.contains("owner"), + "channel notice should say the owner was flagged: {channel}" + ); + } + + #[test] + fn test_gate_notice_fires_once_per_person_per_channel() { + let mut seen = HashSet::new(); + let chan_a = Uuid::from_u128(1); + let chan_b = Uuid::from_u128(2); + + assert!( + remember_gate_notice(&mut seen, (chan_a, "alice".into())), + "first refusal must notify" + ); + assert!( + !remember_gate_notice(&mut seen, (chan_a, "alice".into())), + "a repeat refusal must stay quiet — otherwise a noisy author turns the agent into a flooder" + ); + assert!( + remember_gate_notice(&mut seen, (chan_b, "alice".into())), + "the same person in a different channel has not been told there" + ); + assert!( + remember_gate_notice(&mut seen, (chan_a, "bob".into())), + "a different person in the same channel has not been told" + ); + } + + #[test] + fn test_gate_notice_memo_stays_bounded_under_attacker_supplied_keys() { + // Anyone can mint a pubkey and post, so the key space is not ours to + // trust. The memo must not grow without limit in a long-lived process. + let mut seen = HashSet::new(); + let chan = Uuid::from_u128(7); + for i in 0..(MAX_GATE_NOTIFIED * 2 + 5) { + remember_gate_notice(&mut seen, (chan, format!("author-{i}"))); + assert!( + seen.len() <= MAX_GATE_NOTIFIED, + "memo exceeded its bound at iteration {i}: {}", + seen.len() + ); + } + } + // ── DM hardening ────────────────────────────────────────────────────── // // In a DM, clients auto-p-tag every participant, and an agent can be diff --git a/crates/buzz-acp/src/pool.rs b/crates/buzz-acp/src/pool.rs index f18f7d6fea2..b590830689c 100644 --- a/crates/buzz-acp/src/pool.rs +++ b/crates/buzz-acp/src/pool.rs @@ -4796,6 +4796,64 @@ pub(crate) async fn post_failure_notice( } } +/// Post an in-channel notice that an inbound event was refused by the author gate. +/// +/// Mirrors [`post_failure_notice`] but carries `mentions`, so the owner can be +/// p-tagged and learn through the normal mention path that someone tried to +/// reach the agent. Notices stay in the channel the request arrived in: keeping +/// the exchange with the project preserves its context instead of stranding it +/// in a private inbox, and the agent never opens a DM of its own. +/// +/// Best-effort by design. A refused author is not owed delivery guarantees, and +/// the caller must not be blocked waiting on the relay. +pub(crate) async fn post_gate_notice( + rest: &crate::relay::RestClient, + channel_id: Uuid, + thread_tags: &ThreadTags, + content: &str, + mentions: &[String], +) { + let thread_ref = thread_tags.root_event_id.as_deref().and_then(|root| { + let root_id = nostr::EventId::from_hex(root).ok()?; + let parent_id = thread_tags + .parent_event_id + .as_deref() + .and_then(|p| nostr::EventId::from_hex(p).ok()) + .unwrap_or(root_id); + Some(buzz_sdk::ThreadRef { + root_event_id: root_id, + parent_event_id: parent_id, + }) + }); + let mention_refs: Vec<&str> = mentions.iter().map(String::as_str).collect(); + let builder = match buzz_sdk::build_message( + channel_id, + content, + thread_ref.as_ref(), + &mention_refs, + false, + &[], + ) { + Ok(b) => b, + Err(e) => { + tracing::warn!(channel = %channel_id, "author gate notice: build failed: {e}"); + return; + } + }; + let event = match builder.sign_with_keys(&rest.keys) { + Ok(e) => e, + Err(e) => { + tracing::warn!(channel = %channel_id, "author gate notice: sign failed: {e}"); + return; + } + }; + match tokio::time::timeout(Duration::from_secs(5), rest.submit_event(&event)).await { + Ok(Ok(_)) => {} + Ok(Err(e)) => tracing::warn!(channel = %channel_id, "author gate notice failed: {e}"), + Err(_) => tracing::warn!(channel = %channel_id, "author gate notice timed out"), + } +} + /// Best-effort: remove a reaction via a signed kind:5 (NIP-09) deletion event. /// /// Queries kind:7 reactions by our pubkey targeting the event, finds the matching From 929640b9f7616ed68cfe446810b883e606cc0ff8 Mon Sep 17 00:00:00 2001 From: QuicksilverSlick Date: Sun, 30 Aug 2026 20:29:36 -0500 Subject: [PATCH 02/10] fix(buzz-acp): rewrite failure notices for the person reading them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The four dead-letter notices named the mechanism, leaked internals, and gave instructions the reader usually could not follow: "the turn exceeded the maximum duration (7200s)" "Please re-authenticate the CLI (e.g. run `claude /login`)" format!("{e}") interpolated straight into the channel "Turn", "batch" and a raw seconds count are our vocabulary, not the reader's. Telling a collaborator to run `claude /login` is worse than useless: they have no access to the machine it would run on, so the message describes a fix they cannot perform and names nobody who can. And interpolating a Display impl into a channel can carry paths, hosts or tokens to anyone with read access. Each notice now says what happened, what it means for the reader, and what happens next: - the raw error stays in the log; the channel gets a plain description of the state via failure_reason_text(); - the auth notice says the work is blocked until someone with access restores it, rather than prescribing a command; - notices stop claiming a re-send will help when the retry budget is already spent — that trains people to repeat themselves into silence; - the owner is p-tagged, so a failure the reader cannot fix reaches somebody who can instead of sitting in a channel nobody is watching. post_failure_notice and post_gate_notice were near-identical, so they collapse into one post_notice() carrying mentions. Net -83 lines in pool.rs. Notice routing stays channel-based here. Role-based routing — the requester in their own conversation, the person who can fix it in the project — needs the orchestrator model that does not exist yet, and a message promising delivery the code does not perform would be a regression, not an improvement. Co-Authored-By: Claude Opus 5 --- crates/buzz-acp/src/lib.rs | 167 ++++++++++++++++++++++++++++++------ crates/buzz-acp/src/pool.rs | 75 ++++------------ 2 files changed, 159 insertions(+), 83 deletions(-) diff --git a/crates/buzz-acp/src/lib.rs b/crates/buzz-acp/src/lib.rs index 0f680fd9c0d..7e77c8ee3c8 100644 --- a/crates/buzz-acp/src/lib.rs +++ b/crates/buzz-acp/src/lib.rs @@ -2929,7 +2929,7 @@ async fn tokio_main() -> Result<()> { let rest = ctx.rest_client.clone(); let channel_id = buzz_event.channel_id; tokio::spawn(async move { - pool::post_gate_notice( + pool::post_notice( &rest, channel_id, &thread_tags, @@ -3957,10 +3957,48 @@ fn author_gate_notice_text(is_dm: bool) -> String { } } +/// Mention list for a notice that someone other than the sender must act on. +/// +/// A failure the requester cannot fix has to reach the person who can — a +/// notice nobody is pinged about is only marginally better than no notice. +/// Empty when no owner is known, which degrades to the previous behaviour +/// rather than failing. +fn owner_mentions(owner: Option<&str>) -> Vec { + owner + .filter(|o| !o.is_empty()) + .map(|o| vec![o.to_string()]) + .unwrap_or_default() +} + +/// Plain-language account of why a batch could not be completed. +/// +/// Names the state rather than the mechanism, and never surfaces a raw error +/// string. Internals belong in the log — the person waiting cannot act on a +/// `Display` impl, and a raw error can carry paths or tokens that should not be +/// posted into a channel. +fn failure_reason_text(outcome: &PromptOutcome) -> String { + match outcome { + PromptOutcome::Timeout(TimeoutKind::Idle) => "it stopped making progress".to_string(), + PromptOutcome::Timeout(TimeoutKind::Hard { .. }) => { + "it ran for a long time without getting closer".to_string() + } + PromptOutcome::AgentExited => "the tool I use to do the work stopped unexpectedly".to_string(), + // Deliberately vague to the channel; the detail is in the log. This is + // the one branch that used to interpolate the raw error. + PromptOutcome::Error(_) => "something went wrong on my side".to_string(), + PromptOutcome::ProjectContextIndeterminate(_) => { + "I couldn't tell which project this belongs to, and I'd rather ask than change the wrong one" + .to_string() + } + _ => "it kept failing".to_string(), + } +} + fn spawn_failure_notice( rest_client: Option<&relay::RestClient>, batch: &FlushBatch, content: String, + mentions: Vec, ) { if let Some(rest) = rest_client { let thread_tags = batch @@ -3971,7 +4009,7 @@ fn spawn_failure_notice( let rest = rest.clone(); let channel_id = batch.channel_id; tokio::spawn(async move { - pool::post_failure_notice(&rest, channel_id, &thread_tags, &content).await; + pool::post_notice(&rest, channel_id, &thread_tags, &content, &mentions).await; }); } } @@ -4067,11 +4105,16 @@ fn handle_prompt_result( "dead-lettering batch after hard-cap timeout (no recent activity) — discarding {} events", batch.events.len(), ); - let content = format!( - "⚠️ I couldn't process the last request (the turn exceeded the maximum duration ({}s)). Please re-send if it's still needed.", - config.max_turn_duration_secs + let content = "⚠️ I couldn't finish this one. I worked on it for a long \ + stretch without getting closer, so I stopped rather than keep going — \ + nothing was changed. Send it again if you'd like me to try once more." + .to_string(); + spawn_failure_notice( + rest_client, + &batch, + content, + owner_mentions(config.agent_owner.as_deref()), ); - spawn_failure_notice(rest_client, &batch, content); hard_timeout_fate_suffix = Some(" — dead-lettered (no recent activity)"); } else if matches!( result.outcome, @@ -4085,11 +4128,17 @@ fn handle_prompt_result( "hard-cap timeout with recent activity — requeueing for retry" ); if let Some(dead) = queue.requeue(batch) { - let content = format!( - "⚠️ I couldn't process the last request after multiple retries (the turn exceeded the maximum duration ({}s)). Please re-send if it's still needed.", - config.max_turn_duration_secs + let content = "⚠️ I tried this several times and couldn't get through it, \ + so I've stopped rather than keep failing quietly. Nothing was changed. \ + Sending it again is unlikely to help on its own — this one needs a person \ + to look at it." + .to_string(); + spawn_failure_notice( + rest_client, + &dead, + content, + owner_mentions(config.agent_owner.as_deref()), ); - spawn_failure_notice(rest_client, &dead, content); hard_timeout_fate_suffix = Some(" — dead-lettered (retry budget exhausted)"); } else { hard_timeout_fate_suffix = Some(" — requeued for retry (recently active)"); @@ -4104,26 +4153,32 @@ fn handle_prompt_result( events = batch.events.len(), "dead-lettering batch immediately — non-retryable auth error" ); - let content = "⚠️ I couldn't process the last request: authentication failed. \ - Please re-authenticate the CLI (e.g. run `claude /login` or `codex login`) \ - and then re-send." + let content = "⚠️ I've lost my ability to sign in to the tool I use to do this \ + work, so I can't act on this — or anything else — until that's restored. \ + Nothing was changed. This isn't something you can fix from here: it needs \ + whoever runs this workspace, and I've flagged them on this message." .to_string(); - spawn_failure_notice(rest_client, &batch, content); + spawn_failure_notice( + rest_client, + &batch, + content, + owner_mentions(config.agent_owner.as_deref()), + ); } else if let Some(dead) = queue.requeue(batch) { - let reason = match &result.outcome { - PromptOutcome::Timeout(TimeoutKind::Idle) => "the turn timed out".to_string(), - PromptOutcome::Timeout(TimeoutKind::Hard { .. }) => { - "the turn exceeded the maximum duration".to_string() - } - PromptOutcome::AgentExited => "the agent process exited".to_string(), - PromptOutcome::Error(e) => format!("{e}"), - PromptOutcome::ProjectContextIndeterminate(reason) => reason.clone(), - _ => "repeated failures".to_string(), - }; + // The raw error stays in the log above; what reaches the + // channel names the state, not the mechanism. + let reason = failure_reason_text(&result.outcome); let content = format!( - "⚠️ I couldn't process the last request after multiple retries ({reason}). Please re-send if it's still needed." + "⚠️ I tried this several times and couldn't get through it — {reason}. \ + I've stopped rather than keep failing quietly, and nothing was changed. \ + This one needs a person to look at it." + ); + spawn_failure_notice( + rest_client, + &dead, + content, + owner_mentions(config.agent_owner.as_deref()), ); - spawn_failure_notice(rest_client, &dead, content); } } else { tracing::debug!( @@ -5711,6 +5766,66 @@ mod author_gate_tests { } } + // ── Failure notice wording ──────────────────────────────────────────── + // + // These notices used to name the mechanism ("the turn exceeded the maximum + // duration (7200s)"), interpolate a raw error, and tell whoever was waiting + // to run `claude /login` — an instruction a collaborator cannot act on. + + #[test] + fn test_failure_reason_never_surfaces_a_raw_error() { + // A Display impl can carry paths, tokens, or stack detail. It belongs + // in the log, not in a channel anyone with access can read. + let leaky = PromptOutcome::Error(acp::AcpError::Protocol( + "connect ECONNREFUSED 10.0.0.4:8080 /home/x/.ssh".to_string(), + )); + let text = failure_reason_text(&leaky); + assert!( + !text.contains("ECONNREFUSED") && !text.contains(".ssh") && !text.contains("10.0.0.4"), + "raw error leaked into a user-facing notice: {text}" + ); + } + + #[test] + fn test_failure_reasons_name_the_state_not_the_mechanism() { + let cases = [ + PromptOutcome::Timeout(TimeoutKind::Idle), + PromptOutcome::Timeout(TimeoutKind::Hard { + recently_active: false, + }), + PromptOutcome::AgentExited, + PromptOutcome::Error(acp::AcpError::Protocol("boom".to_string())), + ]; + for outcome in cases { + let text = failure_reason_text(&outcome); + assert!( + !text.is_empty(), + "every outcome needs a plain-language reason" + ); + for jargon in ["turn", "batch", "dead-letter", "PromptOutcome", "requeue"] { + assert!( + !text.to_lowercase().contains(jargon), + "reason leaks internal vocabulary {jargon:?}: {text}" + ); + } + } + } + + #[test] + fn test_owner_is_mentioned_so_a_failure_reaches_someone_who_can_act() { + assert_eq!( + owner_mentions(Some("npub_owner")), + vec!["npub_owner".to_string()], + "a failure the requester cannot fix must ping whoever can" + ); + // Degrade to previous behaviour rather than failing when unknown. + assert!(owner_mentions(None).is_empty()); + assert!( + owner_mentions(Some("")).is_empty(), + "an empty owner must not produce an empty p-tag" + ); + } + // ── DM hardening ────────────────────────────────────────────────────── // // In a DM, clients auto-p-tag every participant, and an agent can be diff --git a/crates/buzz-acp/src/pool.rs b/crates/buzz-acp/src/pool.rs index b590830689c..29f4c814b0b 100644 --- a/crates/buzz-acp/src/pool.rs +++ b/crates/buzz-acp/src/pool.rs @@ -4752,61 +4752,22 @@ pub(crate) async fn reaction_add(rest: &crate::relay::RestClient, event_id: &str } } -/// Best-effort: post a visible failure notice (kind:9) to a channel after a -/// batch is dead-lettered. Replies into the thread of `thread_tags` when the -/// triggering event was threaded. Errors are logged and swallowed — the -/// notice must never take down the main loop. -pub(crate) async fn post_failure_notice( - rest: &crate::relay::RestClient, - channel_id: Uuid, - thread_tags: &ThreadTags, - content: &str, -) { - let thread_ref = thread_tags.root_event_id.as_deref().and_then(|root| { - let root_id = nostr::EventId::from_hex(root).ok()?; - let parent_id = thread_tags - .parent_event_id - .as_deref() - .and_then(|p| nostr::EventId::from_hex(p).ok()) - .unwrap_or(root_id); - Some(buzz_sdk::ThreadRef { - root_event_id: root_id, - parent_event_id: parent_id, - }) - }); - let builder = - match buzz_sdk::build_message(channel_id, content, thread_ref.as_ref(), &[], false, &[]) { - Ok(b) => b, - Err(e) => { - tracing::warn!(channel = %channel_id, "failure notice: build failed: {e}"); - return; - } - }; - let event = match builder.sign_with_keys(&rest.keys) { - Ok(e) => e, - Err(e) => { - tracing::warn!(channel = %channel_id, "failure notice: sign failed: {e}"); - return; - } - }; - match tokio::time::timeout(Duration::from_secs(5), rest.submit_event(&event)).await { - Ok(Ok(_)) => {} - Ok(Err(e)) => tracing::warn!(channel = %channel_id, "failure notice failed: {e}"), - Err(_) => tracing::warn!(channel = %channel_id, "failure notice timed out"), - } -} - -/// Post an in-channel notice that an inbound event was refused by the author gate. +/// Post a notice from the agent into a channel, optionally p-tagging people who +/// need to know. +/// +/// Replies into the thread of `thread_tags` when the triggering event was +/// threaded, so the notice lands beside the request it concerns. Errors are +/// logged and swallowed — a notice must never take down the main loop. /// -/// Mirrors [`post_failure_notice`] but carries `mentions`, so the owner can be -/// p-tagged and learn through the normal mention path that someone tried to -/// reach the agent. Notices stay in the channel the request arrived in: keeping -/// the exchange with the project preserves its context instead of stranding it -/// in a private inbox, and the agent never opens a DM of its own. +/// `mentions` is how a notice reaches the person who can act on it: the owner +/// learns through the normal mention path rather than by watching a channel. +/// Notices stay in the channel the work arrived in — keeping the exchange with +/// its project preserves the context instead of stranding it in a private +/// inbox, and the agent never opens a DM of its own. /// -/// Best-effort by design. A refused author is not owed delivery guarantees, and -/// the caller must not be blocked waiting on the relay. -pub(crate) async fn post_gate_notice( +/// Best-effort by design: the caller must not be blocked waiting on the relay, +/// and a notice that cannot be delivered must not take the turn down with it. +pub(crate) async fn post_notice( rest: &crate::relay::RestClient, channel_id: Uuid, thread_tags: &ThreadTags, @@ -4836,21 +4797,21 @@ pub(crate) async fn post_gate_notice( ) { Ok(b) => b, Err(e) => { - tracing::warn!(channel = %channel_id, "author gate notice: build failed: {e}"); + tracing::warn!(channel = %channel_id, "notice: build failed: {e}"); return; } }; let event = match builder.sign_with_keys(&rest.keys) { Ok(e) => e, Err(e) => { - tracing::warn!(channel = %channel_id, "author gate notice: sign failed: {e}"); + tracing::warn!(channel = %channel_id, "notice: sign failed: {e}"); return; } }; match tokio::time::timeout(Duration::from_secs(5), rest.submit_event(&event)).await { Ok(Ok(_)) => {} - Ok(Err(e)) => tracing::warn!(channel = %channel_id, "author gate notice failed: {e}"), - Err(_) => tracing::warn!(channel = %channel_id, "author gate notice timed out"), + Ok(Err(e)) => tracing::warn!(channel = %channel_id, "notice failed: {e}"), + Err(_) => tracing::warn!(channel = %channel_id, "notice timed out"), } } From 9feb435ba81ebd86a8104edbe147cef13919cc43 Mon Sep 17 00:00:00 2001 From: QuicksilverSlick Date: Mon, 31 Aug 2026 03:42:08 -0500 Subject: [PATCH 03/10] fix(buzz-acp): count events discarded before delivery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two paths in EventQueue::push drop a request somebody sent, and both were observable only as a log line nobody was necessarily reading: - DedupMode::Drop discarded events for an in-flight channel at tracing::debug! — invisible in practice, so work could go missing with no way to establish afterwards that it had; - the per-channel depth cap evicts the OLDEST queued event, meaning the request that has waited longest is the one lost. Neither kept a running total, so "did we lose anything?" was not an answerable question — only "is there a line in the log I happened to catch". Both now increment a DropCounts, logged with the running total the way relay.rs already accounts for gated_observer_dropped. The dedup drop moves from debug! to warn!, matching the depth-cap drop: discarding a request is not a debug-level event. A run that discarded anything says so on shutdown. Scattered mid-run lines are only useful to someone watching at the time; a process that loses work and then exits quietly leaves nothing to answer the question later. drop_counts() exposes the totals so a supervisor outside this queue can observe the loss — the process doing the dropping is the least reliable place to report it from, which is the same reason the watchdog belongs outside the agent. Co-Authored-By: Claude Opus 5 --- crates/buzz-acp/src/lib.rs | 13 ++++ crates/buzz-acp/src/queue.rs | 118 ++++++++++++++++++++++++++++++++++- 2 files changed, 130 insertions(+), 1 deletion(-) diff --git a/crates/buzz-acp/src/lib.rs b/crates/buzz-acp/src/lib.rs index 7e77c8ee3c8..d1587744118 100644 --- a/crates/buzz-acp/src/lib.rs +++ b/crates/buzz-acp/src/lib.rs @@ -3186,6 +3186,19 @@ async fn tokio_main() -> Result<()> { None } _ = shutdown_rx.changed() => { + // A run that discarded work must say so before it exits. + // Otherwise the only trace is scattered mid-run lines that + // nobody was watching, and the loss becomes unanswerable + // after the fact. + let drops = queue.drop_counts(); + if drops.total() > 0 { + tracing::warn!( + in_flight_dedup = drops.in_flight_dedup, + queue_depth_cap = drops.queue_depth_cap, + total = drops.total(), + "shutting down — events were discarded before delivery during this run" + ); + } tracing::info!("shutting down"); break; } diff --git a/crates/buzz-acp/src/queue.rs b/crates/buzz-acp/src/queue.rs index d62b99114cf..2e0e7854e69 100644 --- a/crates/buzz-acp/src/queue.rs +++ b/crates/buzz-acp/src/queue.rs @@ -136,9 +136,35 @@ pub struct FlushBatch { /// if retry_counts[channel] > MAX_RETRIES: dead-letter (log ERROR, return batch to caller) /// else: push_front with original received_at, set exponential backoff retry_after with jitter /// ``` +/// Events discarded before ever reaching an agent, by cause. +/// +/// Both paths lose work somebody is waiting on, so they are counted and not +/// merely logged. A running total is what makes the loss detectable: a +/// watchdog can alert on any non-zero delta without a metrics pipeline, and +/// "this counter moved" is a question anyone can answer from the logs. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub struct DropCounts { + /// Discarded because that channel already had a turn in flight, under + /// [`DedupMode::Drop`]. + pub in_flight_dedup: u64, + /// Discarded because the channel queue was at its depth cap. The *oldest* + /// event is evicted, so the request that has waited longest is the one + /// lost — the reverse of what someone waiting would expect. + pub queue_depth_cap: u64, +} + +impl DropCounts { + /// Total events lost across every cause. + pub fn total(&self) -> u64 { + self.in_flight_dedup + self.queue_depth_cap + } +} + pub struct EventQueue { queues: HashMap>, in_flight_channels: HashSet, + /// Running totals of work discarded before delivery. See [`DropCounts`]. + drops: DropCounts, /// Per-channel deadline for auto-expiring stuck in-flight entries. in_flight_deadlines: HashMap, /// Number of events in each in-flight batch (for expiry logging). @@ -182,6 +208,7 @@ impl EventQueue { Self { queues: HashMap::new(), in_flight_channels: HashSet::new(), + drops: DropCounts::default(), in_flight_deadlines: HashMap::new(), in_flight_batch_sizes: HashMap::new(), retry_after: HashMap::new(), @@ -233,19 +260,27 @@ impl EventQueue { if matches!(self.dedup_mode, DedupMode::Drop) && self.in_flight_channels.contains(&event.channel_id) { - tracing::debug!( + self.drops.in_flight_dedup += 1; + // warn, not debug: this discards a request someone sent. At debug + // level the loss is invisible in practice, which is how work goes + // missing without anyone being able to say so afterwards. + tracing::warn!( channel_id = %event.channel_id, + dropped_total = self.drops.in_flight_dedup, "dropping event for in-flight channel (drop mode)" ); return false; } + let drops = &mut self.drops; let queue = self.queues.entry(event.channel_id).or_default(); // Enforce per-channel depth cap: drop oldest to make room. if queue.len() >= MAX_PENDING_PER_CHANNEL { queue.pop_front(); + drops.queue_depth_cap += 1; tracing::warn!( channel_id = %event.channel_id, limit = MAX_PENDING_PER_CHANNEL, + dropped_total = drops.queue_depth_cap, "queue depth cap reached — dropped oldest event" ); } @@ -626,6 +661,15 @@ impl EventQueue { } /// Number of channels with pending events. + /// Running totals of work discarded before delivery. + /// + /// Exposed so a supervisor outside this queue can observe the loss. A + /// counter only the queue can see is not observability — the process that + /// drops the work is the least reliable place to report it from. + pub fn drop_counts(&self) -> DropCounts { + self.drops + } + pub fn pending_channels(&self) -> usize { self.queues.len() } @@ -2005,6 +2049,78 @@ mod tests { use std::time::Duration; /// Build a test event with the given content and kind. + // ── Discarded-work accounting ───────────────────────────────────── + // + // Both drop paths lose a request someone sent. They were previously + // observable only as scattered log lines — one of them at debug level — + // so work could go missing with no way to establish afterwards that it + // had. These lock the counting down. + + #[test] + fn test_dedup_drop_is_counted_not_just_logged() { + let mut q = EventQueue::new(DedupMode::Drop); + let chan = Uuid::new_v4(); + // flush_next() is what puts a channel in flight. + assert!(q.push(make_queued(chan, "first"))); + let _in_flight = q.flush_next().expect("first push is flushable"); + + assert_eq!(q.drop_counts().total(), 0, "nothing lost yet"); + assert!( + !q.push(make_queued(chan, "dropped one")), + "drop mode discards" + ); + assert!( + !q.push(make_queued(chan, "dropped two")), + "drop mode discards" + ); + + let drops = q.drop_counts(); + assert_eq!( + drops.in_flight_dedup, 2, + "every discarded event must be counted" + ); + assert_eq!(drops.queue_depth_cap, 0, "wrong cause attributed"); + assert_eq!(drops.total(), 2); + } + + #[test] + fn test_queue_mode_does_not_count_drops_because_it_does_not_drop() { + // Queue mode is the CLI default. It must not inflate the loss counter, + // or a real drop becomes indistinguishable from normal operation. + let mut q = EventQueue::new(DedupMode::Queue); + let chan = Uuid::new_v4(); + assert!(q.push(make_queued(chan, "first"))); + let _in_flight = q.flush_next().expect("first push is flushable"); + + assert!(q.push(make_queued(chan, "queued behind in-flight"))); + assert_eq!(q.drop_counts().total(), 0, "queue mode loses nothing"); + } + + #[test] + fn test_depth_cap_drop_is_counted_and_evicts_the_oldest() { + let mut q = EventQueue::new(DedupMode::Queue); + let chan = Uuid::new_v4(); + for i in 0..MAX_PENDING_PER_CHANNEL { + assert!(q.push(make_queued(chan, &format!("event {i}")))); + } + assert_eq!(q.drop_counts().total(), 0, "at the cap, nothing lost yet"); + + // One past the cap evicts the OLDEST — the request that has waited + // longest is the one lost, which is worth asserting because it is the + // opposite of what someone waiting would expect. + assert!(q.push(make_queued(chan, "one too many"))); + let drops = q.drop_counts(); + assert_eq!(drops.queue_depth_cap, 1); + assert_eq!(drops.in_flight_dedup, 0, "wrong cause attributed"); + + let batch = q.flush_next().expect("a batch is pending"); + let first = batch.events.first().expect("batch is non-empty"); + assert!( + !first.event.content.contains("event 0"), + "the oldest event should have been evicted, but it is still at the front" + ); + } + fn make_event(content: &str) -> Event { let keys = Keys::generate(); EventBuilder::new(Kind::Custom(9), content) From 6f51da3f61589175beda056bbb55f37272f37bed Mon Sep 17 00:00:00 2001 From: QuicksilverSlick Date: Mon, 31 Aug 2026 07:30:52 -0500 Subject: [PATCH 04/10] feat(brand): ship as Dreamforge, keeping every name code depends on MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fork is Dreamforge. The rename covers what a person reads and nothing else: RENAMED window title, product name, 228 user-visible strings UNCHANGED crate names (buzz-*), so upstream `use` paths resolve UNCHANGED BUZZ_* environment variables UNCHANGED wire tags (buzz-channel, buzz-protect, buzz-visibility) UNCHANGED event kinds and the app identifier The exclusions are the point. Upstream changed 367,825 lines in the last fortnight, 5,412 of which touch the name: 1,943 crate/module references and 870 environment variables, against ~350 mentions of the bare word. Renaming the first two categories is where a fork breaks. An environment variable is the worst case. Upstream adds BUZZ_NEW_THING, its code reads BUZZ_NEW_THING, and a renamed prefix means it reads a variable nobody sets — compiles, runs, silently takes a default. A wire tag is worse still: no error, just two systems that no longer agree. Display strings have no such coupling. Nothing reads them back. The app identifier stays xyz.block.buzz.app deliberately. Changing it moves the app data directory and keyring service, orphaning the identity and settings of anyone who has already onboarded. It is invisible to users, so the rebrand does not need it. Code comments keep saying Buzz — 108 of them. They are invisible to users and every one rewritten is a merge conflict against upstream for no reader's benefit. Identity surfaces (window title, terminal panel, shared compute) now read from shared/constants/brand.ts. Prose keeps the literal word: turning every sentence into a template literal would add conflict surface without making a future rename meaningfully easier. Verified: tsc clean, vite build clean, and biome flags exactly the same 7 pre-existing files as before the change. Co-Authored-By: Claude Opus 5 --- README.md | 16 ++ ...lTempbuzz-acp-steer-captureacp_shape.json" | 0 ...buzz-acp-steer-capturegoose_priority.json" | 0 ...mpbuzz-acp-steer-captureno_transport.json" | 0 ...buzz-acp-steer-captureoutcome_absent.json" | 0 ...buzz-acp-steer-captureoutcome_failed.json" | 0 ...14e547-fcbc-4964-ac73-cc39825f5cba.ndjson" | 2 + ...cc5e54-d9c1-45ea-a422-1b0967912ee8.ndjson" | 2 + ...24be64-44bc-4b53-8970-461f683422bf.ndjson" | 2 + ...3d29d8-606f-4ed4-be7c-ab61079e7d3e.ndjson" | 2 + ...9e13b6-620e-4dc4-8da8-1c3b5f55e01c.ndjson" | 2 + ...1482d2-7183-459d-b959-ab311f2e4eaa.ndjson" | 1 + ...8c3a05-fed4-4329-ad4e-0a64c0a768e9.ndjson" | 1 + ...aa4770-3a63-47f3-814b-d2828f07ac70.ndjson" | 1 + ...a7efad-ce54-4a49-84df-e5ff197ecc5c.ndjson" | 1 + ...a9651f-8084-4073-8951-079e542d6704.ndjson" | 1 + ...26fe48-4d4c-4792-97a0-d184a35ab949.ndjson" | 2 + ...33391f-3465-4c74-ad9b-c918582a5a86.ndjson" | 2 + ...c05edd-9f73-4c25-85a9-322737290eb7.ndjson" | 2 + ...0aab02-2f7f-4bad-9f47-e0d1d433cc67.ndjson" | 2 + ...f49afc-0711-441b-99c2-ee72ee55bcb5.ndjson" | 2 + ...35dd72-0cd8-4264-8a28-baebd390ba19.ndjson" | 2 + ...0fdd4b-cfba-40ca-ae02-642c750d3282.ndjson" | 2 + ...b95968-5472-4e75-b12c-6780029c5a51.ndjson" | 2 + ...123208-44a0-4dd0-8fcd-11add8e7cd8f.ndjson" | 2 + ...e64bcb-9bd1-47f5-b77d-0761d9044b6a.ndjson" | 2 + desktop/src-tauri/tauri.conf.json | 2 +- desktop/src/app/RootErrorBoundary.tsx | 6 +- .../app/useCommunityNavigationTransitions.ts | 2 +- .../agents/ui/AgentCardMintDialog.tsx | 4 +- .../agents/ui/OtherSetupAgentMarker.tsx | 2 +- .../features/agents/ui/RestartDiffBadge.tsx | 2 +- .../features/agents/ui/agentConfigOptions.tsx | 5 +- .../agents/ui/agentProfileSyncWarning.ts | 2 +- .../agents/ui/agentSessionToolCatalog.ts | 18 +- .../agents/ui/agentSessionToolClassifier.ts | 6 +- .../ui/agentSessionTranscriptGrouping.ts | 2 +- .../ui/agentSessionTranscriptHelpers.ts | 8 +- .../agents/ui/personaModelDiscoveryStatus.ts | 8 +- .../channels/ui/ChannelScreenHeader.tsx | 7 +- .../features/communities/communityStorage.ts | 2 +- .../communities/hostedCommunityApi.ts | 13 +- .../communities/ui/AddCommunityDialog.tsx | 2 +- .../communities/ui/CommunitySwitcher.tsx | 2 +- .../ui/HostedCommunityCreateFlow.tsx | 22 +-- .../ui/HostedCommunityOnboarding.tsx | 30 +-- .../ui/CustomEmojiSettingsCard.tsx | 4 +- .../ui/LocalArchiveSettingsCard.tsx | 2 +- .../ui/MeshComputeSettingsCard.tsx | 5 +- .../messages/lib/composerMessageLinkNode.ts | 4 +- desktop/src/features/notifications/hooks.ts | 2 +- .../notifications/lib/notificationFormat.ts | 4 +- .../src/features/onboarding/ui/BackupStep.tsx | 16 +- .../features/onboarding/ui/BackupTestFlow.tsx | 2 +- .../onboarding/ui/CommunityOnboardingFlow.tsx | 7 +- .../onboarding/ui/DefaultConfigStep.tsx | 8 +- .../onboarding/ui/DownloadKeyStep.tsx | 2 +- .../onboarding/ui/IdentityKeyHelpDialog.tsx | 17 +- .../onboarding/ui/IdentityRecoveryPairing.tsx | 4 +- .../onboarding/ui/JoinPolicyNotice.tsx | 2 +- .../onboarding/ui/KeyringLockedScreen.tsx | 4 +- .../onboarding/ui/MachineOnboardingFlow.tsx | 13 +- .../features/onboarding/ui/OnboardingFlow.tsx | 10 +- .../features/onboarding/ui/ProfileStep.tsx | 4 +- .../features/onboarding/ui/RecoveryScreen.tsx | 2 +- .../onboarding/ui/RelaunchRequiredScreen.tsx | 4 +- .../onboarding/ui/ResetFailedScreen.tsx | 2 +- .../features/onboarding/ui/RuntimeIcon.tsx | 2 +- .../src/features/onboarding/ui/SetupStep.tsx | 8 +- .../src/features/onboarding/welcomeCanvas.ts | 4 +- .../src/features/onboarding/welcomeGuide.ts | 2 +- .../src/features/onboarding/welcomeKickoff.ts | 4 +- .../profile/ui/AnimatedAvatarCapture.tsx | 2 +- .../profile/ui/NostrBindConsentDialog.tsx | 15 +- .../ui/UserProfileAgentManagementRows.tsx | 2 +- .../projects/lib/projectDetailAgentContext.ts | 2 +- .../features/projects/lib/projectGitError.ts | 4 +- .../projects/lib/projectHomeTemplate.ts | 2 +- .../projects/lib/projectRepoAvailability.ts | 8 +- .../ui/AgentContextPayloadPreview.tsx | 4 +- .../src/features/projects/ui/ProjectCards.tsx | 8 +- .../projects/ui/ProjectDetailFeedPanels.tsx | 6 +- .../projects/ui/ProjectReadmePanel.tsx | 2 +- .../projects/ui/ProjectRightPanelControls.tsx | 6 +- .../projects/ui/ProjectWorkspaceTabs.tsx | 2 +- .../projects/ui/useOpenProjectTerminal.ts | 2 +- .../ui/useProjectRepositoryOpenActions.ts | 6 +- .../features/projects/useAddProjectChannel.ts | 2 +- .../settings/EncryptedBackupProvider.tsx | 2 +- .../src/features/settings/UpdateChecker.tsx | 2 +- .../settings/hooks/useSendFeedback.ts | 2 +- .../ui/AppearanceSettingsControls.tsx | 4 +- .../features/settings/ui/BackupTestFlow.tsx | 2 +- .../settings/ui/EncryptedBackupCreator.tsx | 10 +- .../settings/ui/HarnessesSettingsPanel.tsx | 2 +- .../ui/HostedCommunitiesSettingsCard.tsx | 59 +++--- .../settings/ui/MobilePairingCard.tsx | 8 +- .../settings/ui/ProfileSettingsCard.tsx | 2 +- .../settings/ui/SendFeedbackDialog.tsx | 4 +- .../features/settings/ui/SettingsPanels.tsx | 8 +- .../features/settings/ui/SignOutSection.tsx | 4 +- .../settings/ui/VoiceSettingsCard.tsx | 2 +- .../settings/ui/harnessCatalogCopy.ts | 7 +- .../settings/ui/harnessCatalogLogic.ts | 2 +- .../features/terminal/TerminalSubstrate.tsx | 14 +- .../src/features/terminal/terminalClient.ts | 3 +- desktop/src/shared/constants/brand.ts | 37 ++++ desktop/src/shared/lib/linkPreview.ts | 10 +- desktop/src/shared/lib/localStorageQuota.ts | 2 +- .../shared/ui/buzz-logo/BuzzLogoAnimation.tsx | 2 +- desktop/src/shared/ui/buzz-logo/FuzzyLogo.tsx | 2 +- desktop/src/testing/e2eBridge.ts | 18 +- desktop/tsconfig.node.tsbuildinfo | 1 + desktop/tsconfig.tsbuildinfo | 1 + docker-compose.relay.yml | 82 +++++++++ scripts/build-windows-installer.bat | 85 +++++++++ scripts/dev-windows.bat | 173 ++++++++++++++++++ web/index.html | 2 +- .../invite/ui/InviteJoinPolicyNotice.tsx | 4 +- web/src/features/invite/ui/InvitePage.tsx | 6 +- web/src/features/repos/mock-repos.ts | 4 +- web/src/features/repos/ui/ConnectButton.tsx | 2 +- web/src/features/repos/ui/OrgSidebar.tsx | 2 +- web/src/features/repos/ui/RepoDetailPage.tsx | 2 +- web/src/features/repos/ui/ReposPage.tsx | 4 +- 125 files changed, 718 insertions(+), 265 deletions(-) create mode 100644 "crates/buzz-acp/C\357\200\272UsersPCOWNE~1AppDataLocalTempbuzz-acp-steer-captureacp_shape.json" create mode 100644 "crates/buzz-acp/C\357\200\272UsersPCOWNE~1AppDataLocalTempbuzz-acp-steer-capturegoose_priority.json" create mode 100644 "crates/buzz-acp/C\357\200\272UsersPCOWNE~1AppDataLocalTempbuzz-acp-steer-captureno_transport.json" create mode 100644 "crates/buzz-acp/C\357\200\272UsersPCOWNE~1AppDataLocalTempbuzz-acp-steer-captureoutcome_absent.json" create mode 100644 "crates/buzz-acp/C\357\200\272UsersPCOWNE~1AppDataLocalTempbuzz-acp-steer-captureoutcome_failed.json" create mode 100644 "crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-channel-delivery-lifecycle-0114e547-fcbc-4964-ac73-cc39825f5cba.ndjson" create mode 100644 "crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-channel-delivery-lifecycle-09cc5e54-d9c1-45ea-a422-1b0967912ee8.ndjson" create mode 100644 "crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-channel-delivery-lifecycle-5d24be64-44bc-4b53-8970-461f683422bf.ndjson" create mode 100644 "crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-channel-delivery-lifecycle-7d3d29d8-606f-4ed4-be7c-ab61079e7d3e.ndjson" create mode 100644 "crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-channel-delivery-lifecycle-7e9e13b6-620e-4dc4-8da8-1c3b5f55e01c.ndjson" create mode 100644 "crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-late-steer-wire-031482d2-7183-459d-b959-ab311f2e4eaa.ndjson" create mode 100644 "crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-late-steer-wire-548c3a05-fed4-4329-ad4e-0a64c0a768e9.ndjson" create mode 100644 "crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-late-steer-wire-e9aa4770-3a63-47f3-814b-d2828f07ac70.ndjson" create mode 100644 "crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-late-steer-wire-efa7efad-ce54-4a49-84df-e5ff197ecc5c.ndjson" create mode 100644 "crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-late-steer-wire-fca9651f-8084-4073-8951-079e542d6704.ndjson" create mode 100644 "crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-merged-delivery-wire-0126fe48-4d4c-4792-97a0-d184a35ab949.ndjson" create mode 100644 "crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-merged-delivery-wire-0433391f-3465-4c74-ad9b-c918582a5a86.ndjson" create mode 100644 "crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-merged-delivery-wire-1fc05edd-9f73-4c25-85a9-322737290eb7.ndjson" create mode 100644 "crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-merged-delivery-wire-900aab02-2f7f-4bad-9f47-e0d1d433cc67.ndjson" create mode 100644 "crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-merged-delivery-wire-e3f49afc-0711-441b-99c2-ee72ee55bcb5.ndjson" create mode 100644 "crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-standing-lifecycle-3735dd72-0cd8-4264-8a28-baebd390ba19.ndjson" create mode 100644 "crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-standing-lifecycle-600fdd4b-cfba-40ca-ae02-642c750d3282.ndjson" create mode 100644 "crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-standing-lifecycle-61b95968-5472-4e75-b12c-6780029c5a51.ndjson" create mode 100644 "crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-standing-lifecycle-97123208-44a0-4dd0-8fcd-11add8e7cd8f.ndjson" create mode 100644 "crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-standing-lifecycle-a5e64bcb-9bd1-47f5-b77d-0761d9044b6a.ndjson" create mode 100644 desktop/src/shared/constants/brand.ts create mode 100644 desktop/tsconfig.node.tsbuildinfo create mode 100644 desktop/tsconfig.tsbuildinfo create mode 100644 docker-compose.relay.yml create mode 100644 scripts/build-windows-installer.bat create mode 100644 scripts/dev-windows.bat diff --git a/README.md b/README.md index 56439f00bc1..261823c5e3b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,19 @@ +> **This is Dreamforge**, a fork of [`block/buzz`](https://github.com/block/buzz). +> +> The product ships as Dreamforge and every screen says so. Everything below the +> surface deliberately keeps upstream's names — crates stay `buzz-*`, environment +> variables stay `BUZZ_*`, and on-the-wire tags (`buzz-channel`, `buzz-protect`, +> `buzz-visibility`) and event kinds are untouched. +> +> That split is intentional. Renaming an environment variable would make upstream +> code read one nobody sets — it compiles, runs, and silently takes a default. +> Renaming a wire tag would diverge the protocol with no error at all. Both fail +> quietly, and quiet failure is the thing this fork exists to remove. Renaming +> what people read costs nothing, because no code depends on it. +> +> The product name lives in `desktop/src/shared/constants/brand.ts`. +> Upstream documentation below is unchanged and still refers to Buzz. +

Buzz 🐝

diff --git "a/crates/buzz-acp/C\357\200\272UsersPCOWNE~1AppDataLocalTempbuzz-acp-steer-captureacp_shape.json" "b/crates/buzz-acp/C\357\200\272UsersPCOWNE~1AppDataLocalTempbuzz-acp-steer-captureacp_shape.json" new file mode 100644 index 00000000000..e69de29bb2d diff --git "a/crates/buzz-acp/C\357\200\272UsersPCOWNE~1AppDataLocalTempbuzz-acp-steer-capturegoose_priority.json" "b/crates/buzz-acp/C\357\200\272UsersPCOWNE~1AppDataLocalTempbuzz-acp-steer-capturegoose_priority.json" new file mode 100644 index 00000000000..e69de29bb2d diff --git "a/crates/buzz-acp/C\357\200\272UsersPCOWNE~1AppDataLocalTempbuzz-acp-steer-captureno_transport.json" "b/crates/buzz-acp/C\357\200\272UsersPCOWNE~1AppDataLocalTempbuzz-acp-steer-captureno_transport.json" new file mode 100644 index 00000000000..e69de29bb2d diff --git "a/crates/buzz-acp/C\357\200\272UsersPCOWNE~1AppDataLocalTempbuzz-acp-steer-captureoutcome_absent.json" "b/crates/buzz-acp/C\357\200\272UsersPCOWNE~1AppDataLocalTempbuzz-acp-steer-captureoutcome_absent.json" new file mode 100644 index 00000000000..e69de29bb2d diff --git "a/crates/buzz-acp/C\357\200\272UsersPCOWNE~1AppDataLocalTempbuzz-acp-steer-captureoutcome_failed.json" "b/crates/buzz-acp/C\357\200\272UsersPCOWNE~1AppDataLocalTempbuzz-acp-steer-captureoutcome_failed.json" new file mode 100644 index 00000000000..e69de29bb2d diff --git "a/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-channel-delivery-lifecycle-0114e547-fcbc-4964-ac73-cc39825f5cba.ndjson" "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-channel-delivery-lifecycle-0114e547-fcbc-4964-ac73-cc39825f5cba.ndjson" new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-channel-delivery-lifecycle-0114e547-fcbc-4964-ac73-cc39825f5cba.ndjson" @@ -0,0 +1,2 @@ + + diff --git "a/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-channel-delivery-lifecycle-09cc5e54-d9c1-45ea-a422-1b0967912ee8.ndjson" "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-channel-delivery-lifecycle-09cc5e54-d9c1-45ea-a422-1b0967912ee8.ndjson" new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-channel-delivery-lifecycle-09cc5e54-d9c1-45ea-a422-1b0967912ee8.ndjson" @@ -0,0 +1,2 @@ + + diff --git "a/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-channel-delivery-lifecycle-5d24be64-44bc-4b53-8970-461f683422bf.ndjson" "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-channel-delivery-lifecycle-5d24be64-44bc-4b53-8970-461f683422bf.ndjson" new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-channel-delivery-lifecycle-5d24be64-44bc-4b53-8970-461f683422bf.ndjson" @@ -0,0 +1,2 @@ + + diff --git "a/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-channel-delivery-lifecycle-7d3d29d8-606f-4ed4-be7c-ab61079e7d3e.ndjson" "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-channel-delivery-lifecycle-7d3d29d8-606f-4ed4-be7c-ab61079e7d3e.ndjson" new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-channel-delivery-lifecycle-7d3d29d8-606f-4ed4-be7c-ab61079e7d3e.ndjson" @@ -0,0 +1,2 @@ + + diff --git "a/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-channel-delivery-lifecycle-7e9e13b6-620e-4dc4-8da8-1c3b5f55e01c.ndjson" "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-channel-delivery-lifecycle-7e9e13b6-620e-4dc4-8da8-1c3b5f55e01c.ndjson" new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-channel-delivery-lifecycle-7e9e13b6-620e-4dc4-8da8-1c3b5f55e01c.ndjson" @@ -0,0 +1,2 @@ + + diff --git "a/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-late-steer-wire-031482d2-7183-459d-b959-ab311f2e4eaa.ndjson" "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-late-steer-wire-031482d2-7183-459d-b959-ab311f2e4eaa.ndjson" new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-late-steer-wire-031482d2-7183-459d-b959-ab311f2e4eaa.ndjson" @@ -0,0 +1 @@ + diff --git "a/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-late-steer-wire-548c3a05-fed4-4329-ad4e-0a64c0a768e9.ndjson" "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-late-steer-wire-548c3a05-fed4-4329-ad4e-0a64c0a768e9.ndjson" new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-late-steer-wire-548c3a05-fed4-4329-ad4e-0a64c0a768e9.ndjson" @@ -0,0 +1 @@ + diff --git "a/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-late-steer-wire-e9aa4770-3a63-47f3-814b-d2828f07ac70.ndjson" "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-late-steer-wire-e9aa4770-3a63-47f3-814b-d2828f07ac70.ndjson" new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-late-steer-wire-e9aa4770-3a63-47f3-814b-d2828f07ac70.ndjson" @@ -0,0 +1 @@ + diff --git "a/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-late-steer-wire-efa7efad-ce54-4a49-84df-e5ff197ecc5c.ndjson" "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-late-steer-wire-efa7efad-ce54-4a49-84df-e5ff197ecc5c.ndjson" new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-late-steer-wire-efa7efad-ce54-4a49-84df-e5ff197ecc5c.ndjson" @@ -0,0 +1 @@ + diff --git "a/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-late-steer-wire-fca9651f-8084-4073-8951-079e542d6704.ndjson" "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-late-steer-wire-fca9651f-8084-4073-8951-079e542d6704.ndjson" new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-late-steer-wire-fca9651f-8084-4073-8951-079e542d6704.ndjson" @@ -0,0 +1 @@ + diff --git "a/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-merged-delivery-wire-0126fe48-4d4c-4792-97a0-d184a35ab949.ndjson" "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-merged-delivery-wire-0126fe48-4d4c-4792-97a0-d184a35ab949.ndjson" new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-merged-delivery-wire-0126fe48-4d4c-4792-97a0-d184a35ab949.ndjson" @@ -0,0 +1,2 @@ + + diff --git "a/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-merged-delivery-wire-0433391f-3465-4c74-ad9b-c918582a5a86.ndjson" "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-merged-delivery-wire-0433391f-3465-4c74-ad9b-c918582a5a86.ndjson" new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-merged-delivery-wire-0433391f-3465-4c74-ad9b-c918582a5a86.ndjson" @@ -0,0 +1,2 @@ + + diff --git "a/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-merged-delivery-wire-1fc05edd-9f73-4c25-85a9-322737290eb7.ndjson" "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-merged-delivery-wire-1fc05edd-9f73-4c25-85a9-322737290eb7.ndjson" new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-merged-delivery-wire-1fc05edd-9f73-4c25-85a9-322737290eb7.ndjson" @@ -0,0 +1,2 @@ + + diff --git "a/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-merged-delivery-wire-900aab02-2f7f-4bad-9f47-e0d1d433cc67.ndjson" "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-merged-delivery-wire-900aab02-2f7f-4bad-9f47-e0d1d433cc67.ndjson" new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-merged-delivery-wire-900aab02-2f7f-4bad-9f47-e0d1d433cc67.ndjson" @@ -0,0 +1,2 @@ + + diff --git "a/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-merged-delivery-wire-e3f49afc-0711-441b-99c2-ee72ee55bcb5.ndjson" "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-merged-delivery-wire-e3f49afc-0711-441b-99c2-ee72ee55bcb5.ndjson" new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-merged-delivery-wire-e3f49afc-0711-441b-99c2-ee72ee55bcb5.ndjson" @@ -0,0 +1,2 @@ + + diff --git "a/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-standing-lifecycle-3735dd72-0cd8-4264-8a28-baebd390ba19.ndjson" "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-standing-lifecycle-3735dd72-0cd8-4264-8a28-baebd390ba19.ndjson" new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-standing-lifecycle-3735dd72-0cd8-4264-8a28-baebd390ba19.ndjson" @@ -0,0 +1,2 @@ + + diff --git "a/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-standing-lifecycle-600fdd4b-cfba-40ca-ae02-642c750d3282.ndjson" "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-standing-lifecycle-600fdd4b-cfba-40ca-ae02-642c750d3282.ndjson" new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-standing-lifecycle-600fdd4b-cfba-40ca-ae02-642c750d3282.ndjson" @@ -0,0 +1,2 @@ + + diff --git "a/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-standing-lifecycle-61b95968-5472-4e75-b12c-6780029c5a51.ndjson" "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-standing-lifecycle-61b95968-5472-4e75-b12c-6780029c5a51.ndjson" new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-standing-lifecycle-61b95968-5472-4e75-b12c-6780029c5a51.ndjson" @@ -0,0 +1,2 @@ + + diff --git "a/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-standing-lifecycle-97123208-44a0-4dd0-8fcd-11add8e7cd8f.ndjson" "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-standing-lifecycle-97123208-44a0-4dd0-8fcd-11add8e7cd8f.ndjson" new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-standing-lifecycle-97123208-44a0-4dd0-8fcd-11add8e7cd8f.ndjson" @@ -0,0 +1,2 @@ + + diff --git "a/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-standing-lifecycle-a5e64bcb-9bd1-47f5-b77d-0761d9044b6a.ndjson" "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-standing-lifecycle-a5e64bcb-9bd1-47f5-b77d-0761d9044b6a.ndjson" new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ "b/crates/buzz-acp/C\357\200\272\357\201\234Users\357\201\234PCOWNE~1\357\201\234AppData\357\201\234Local\357\201\234Temp\357\201\234buzz-acp-standing-lifecycle-a5e64bcb-9bd1-47f5-b77d-0761d9044b6a.ndjson" @@ -0,0 +1,2 @@ + + diff --git a/desktop/src-tauri/tauri.conf.json b/desktop/src-tauri/tauri.conf.json index 05dc5553397..a8feed22311 100644 --- a/desktop/src-tauri/tauri.conf.json +++ b/desktop/src-tauri/tauri.conf.json @@ -1,6 +1,6 @@ { "$schema": "https://schema.tauri.app/config/2", - "productName": "Buzz", + "productName": "Dreamforge", "version": "0.5.20", "identifier": "xyz.block.buzz.app", "build": { diff --git a/desktop/src/app/RootErrorBoundary.tsx b/desktop/src/app/RootErrorBoundary.tsx index 0c4bbe80b0f..7763646082d 100644 --- a/desktop/src/app/RootErrorBoundary.tsx +++ b/desktop/src/app/RootErrorBoundary.tsx @@ -38,10 +38,10 @@ export class RootErrorBoundary extends Component< if (error) { return (

-

Buzz failed to start

+

Dreamforge failed to start

- Reload Buzz to try again. If this keeps happening, check that Buzz - can access website data, then contact support. + Reload Dreamforge to try again. If this keeps happening, check that + Dreamforge can access website data, then contact support.

)} - {/* Quiet breadcrumb: Buzz itself is open source; this hosted + {/* Quiet breadcrumb: Dreamforge itself is open source; this hosted relay is the one account-backed piece of the flow. */}

- Buzz is open source. Builderlab hosts the relay for this + Dreamforge is open source. Builderlab hosts the relay for this account.

) : !identity ? ( <> - Finish connecting Buzz + Finish connecting Dreamforge Your Builderlab account {auth.email ? ` (${auth.email})` : ""} is ready. Connect this - device’s Buzz identity to finish setup. Your private key stays - on this device. + device’s Dreamforge identity to finish setup. Your private key + stays on this device. {errorBox ?
{errorBox}
: null} {starterChannelFailureCount >= 2 ? ( diff --git a/desktop/src/features/onboarding/ui/DefaultConfigStep.tsx b/desktop/src/features/onboarding/ui/DefaultConfigStep.tsx index 1fd2e4bcabd..acba94ee161 100644 --- a/desktop/src/features/onboarding/ui/DefaultConfigStep.tsx +++ b/desktop/src/features/onboarding/ui/DefaultConfigStep.tsx @@ -43,7 +43,7 @@ type DefaultConfigStepProps = { function formatHarnessLabel(runtime: AcpRuntimeCatalogEntry | undefined) { if (!runtime) return "Select a harness"; - return runtime.id === "buzz-agent" ? "Buzz" : runtime.label; + return runtime.id === "buzz-agent" ? "Dreamforge" : runtime.label; } function AgentDefaultsSection({ @@ -357,9 +357,9 @@ export function DefaultConfigStep({ Configure your default model settings

- This will be set as your default model configuration across Buzz. You - can always change this in your Settings or give specific agents a - different configuration. + This will be set as your default model configuration across + Dreamforge. You can always change this in your Settings or give + specific agents a different configuration.

diff --git a/desktop/src/features/onboarding/ui/DownloadKeyStep.tsx b/desktop/src/features/onboarding/ui/DownloadKeyStep.tsx index d91bc92e61a..3e88151adbb 100644 --- a/desktop/src/features/onboarding/ui/DownloadKeyStep.tsx +++ b/desktop/src/features/onboarding/ui/DownloadKeyStep.tsx @@ -80,7 +80,7 @@ export function DownloadKeyStep({ ? "Now enter your password to prove you can unlock it." : hasCreated ? "Learn how your backup works. Drop the file you just saved and unlock it with your password." - : "Keep the downloaded file private — you need both it and your password to restore your identity. Save the backup password somewhere safe; Buzz cannot reset it if lost."} + : "Keep the downloaded file private — you need both it and your password to restore your identity. Save the backup password somewhere safe; Dreamforge cannot reset it if lost."}

diff --git a/desktop/src/features/onboarding/ui/IdentityKeyHelpDialog.tsx b/desktop/src/features/onboarding/ui/IdentityKeyHelpDialog.tsx index 0abcd77cb02..9fce3476934 100644 --- a/desktop/src/features/onboarding/ui/IdentityKeyHelpDialog.tsx +++ b/desktop/src/features/onboarding/ui/IdentityKeyHelpDialog.tsx @@ -82,18 +82,19 @@ export function IdentityKeyHelpDialog() { >

- Buzz uses an identity key instead of a traditional account. It’s - created on your device and represents you whenever you use Buzz. + Dreamforge uses an identity key instead of a traditional + account. It’s created on your device and represents you whenever + you use Dreamforge.

- Your identity belongs to you, not Buzz. There’s no password to - reset, and Buzz can’t recover your key if you lose it. Keep a - backup somewhere safe and never share it. Anyone with your key - can act as you. + Your identity belongs to you, not Dreamforge. There’s no + password to reset, and Dreamforge can’t recover your key if you + lose it. Keep a backup somewhere safe and never share it. Anyone + with your key can act as you.

- If you’re new to Buzz, create a new identity key. If you already - have a Nostr identity, use your existing key. + If you’re new to Dreamforge, create a new identity key. If you + already have a Nostr identity, use your existing key.

diff --git a/desktop/src/features/onboarding/ui/IdentityRecoveryPairing.tsx b/desktop/src/features/onboarding/ui/IdentityRecoveryPairing.tsx index 5cca7c1bf5e..0058d1ff61d 100644 --- a/desktop/src/features/onboarding/ui/IdentityRecoveryPairing.tsx +++ b/desktop/src/features/onboarding/ui/IdentityRecoveryPairing.tsx @@ -188,8 +188,8 @@ export function IdentityRecoveryPairing({

- This gives this desktop permanent access to your Buzz identity. - Only continue if you trust it. + This gives this desktop permanent access to your Dreamforge + identity. Only continue if you trust it.

diff --git a/desktop/src/features/onboarding/ui/RelaunchRequiredScreen.tsx b/desktop/src/features/onboarding/ui/RelaunchRequiredScreen.tsx index f271b5d1571..9c2af476c25 100644 --- a/desktop/src/features/onboarding/ui/RelaunchRequiredScreen.tsx +++ b/desktop/src/features/onboarding/ui/RelaunchRequiredScreen.tsx @@ -4,8 +4,8 @@ export function RelaunchRequiredScreen() { return ( ); } diff --git a/desktop/src/features/onboarding/ui/ResetFailedScreen.tsx b/desktop/src/features/onboarding/ui/ResetFailedScreen.tsx index 8d87e980062..37114cbb585 100644 --- a/desktop/src/features/onboarding/ui/ResetFailedScreen.tsx +++ b/desktop/src/features/onboarding/ui/ResetFailedScreen.tsx @@ -5,7 +5,7 @@ export function ResetFailedScreen() { ); } diff --git a/desktop/src/features/onboarding/ui/RuntimeIcon.tsx b/desktop/src/features/onboarding/ui/RuntimeIcon.tsx index 5b247c31f73..e5c05107f56 100644 --- a/desktop/src/features/onboarding/ui/RuntimeIcon.tsx +++ b/desktop/src/features/onboarding/ui/RuntimeIcon.tsx @@ -34,7 +34,7 @@ function isBuzzRuntime(runtime: AcpRuntimeCatalogEntry): boolean { export function getRuntimeDisplayLabel( runtime: AcpRuntimeCatalogEntry, ): string { - return isBuzzRuntime(runtime) ? "Buzz" : runtime.label; + return isBuzzRuntime(runtime) ? "Dreamforge" : runtime.label; } function getRuntimeLogoUrl(runtime: AcpRuntimeCatalogEntry): string | null { diff --git a/desktop/src/features/onboarding/ui/SetupStep.tsx b/desktop/src/features/onboarding/ui/SetupStep.tsx index aac9b53846a..9b9b78cd67d 100644 --- a/desktop/src/features/onboarding/ui/SetupStep.tsx +++ b/desktop/src/features/onboarding/ui/SetupStep.tsx @@ -370,8 +370,8 @@ function RuntimeDetails({ runtime }: { runtime: AcpRuntimeCatalogEntry }) { codex-acp {" "} - adapter. Older Buzz releases using the legacy adapter contract may - lose community access until{" "} + adapter. Older Dreamforge releases using the legacy adapter contract + may lose community access until{" "} @zed-industries/codex-acp@0.16.0 {" "} @@ -661,8 +661,8 @@ function RuntimeProvidersSection({ Set up your agent harnesses

- Buzz checks for command-line harnesses on this machine. Install the - CLI or sign in to at least one to continue. + Dreamforge checks for command-line harnesses on this machine. Install + the CLI or sign in to at least one to continue.

diff --git a/desktop/src/features/onboarding/welcomeCanvas.ts b/desktop/src/features/onboarding/welcomeCanvas.ts index bdd8ff6371c..0c84bbe02fa 100644 --- a/desktop/src/features/onboarding/welcomeCanvas.ts +++ b/desktop/src/features/onboarding/welcomeCanvas.ts @@ -1,6 +1,6 @@ import { getCanvas, setCanvas } from "@/shared/api/tauri"; -export const WELCOME_CANVAS_CONTENT = `# Welcome to Buzz +export const WELCOME_CANVAS_CONTENT = `# Welcome to Dreamforge This private channel is your home base for getting oriented. Fizz, Honey, and Pollen can help you learn the app, troubleshoot setup, and work through something you are building. @@ -16,7 +16,7 @@ Bring the team something you are building, or give them a quick challenge to see ## Get help -Ask the team a question here, or read the [Buzz user guide](https://github.com/block/buzz#readme). +Ask the team a question here, or read the [Dreamforge user guide](https://github.com/block/buzz#readme). `; type WelcomeCanvasClient = { diff --git a/desktop/src/features/onboarding/welcomeGuide.ts b/desktop/src/features/onboarding/welcomeGuide.ts index a6057025f24..7a09d66caf6 100644 --- a/desktop/src/features/onboarding/welcomeGuide.ts +++ b/desktop/src/features/onboarding/welcomeGuide.ts @@ -30,7 +30,7 @@ const LEGACY_WELCOME_GUIDE_AGENT_NAME = "Kit"; export const LEGACY_WELCOME_GUIDE_SYSTEM_PROMPT = "You are Kit, Sprout's friendly welcome guide. Help new users understand the community, channels, messages, and agents. Keep introductions concise, practical, and warm."; export const WELCOME_GUIDE_INTRO_MESSAGE = - "Hi, I'm Fizz. Welcome to Buzz.\n\nI can help you get oriented, answer questions, and make the first few steps feel less mysterious.\n\nFeel free to ask me what else you can do in Buzz, or just talk through what you want to build."; + "Hi, I'm Fizz. Welcome to Dreamforge.\n\nI can help you get oriented, answer questions, and make the first few steps feel less mysterious.\n\nFeel free to ask me what else you can do in Dreamforge, or just talk through what you want to build."; export type WelcomeTeamRole = "lead" | "teammate"; diff --git a/desktop/src/features/onboarding/welcomeKickoff.ts b/desktop/src/features/onboarding/welcomeKickoff.ts index f46aeb3b213..bb0a900ce49 100644 --- a/desktop/src/features/onboarding/welcomeKickoff.ts +++ b/desktop/src/features/onboarding/welcomeKickoff.ts @@ -179,10 +179,10 @@ export function buildWelcomeKickoffOpener( if (introTeammates.length === 0) { const teammateNames = formatAgentNames(allTeammates); const teammatePhrase = teammateNames ? ` with ${teammateNames}` : ""; - return `${greeting} Welcome to Buzz. This is your private home base, and I'm here${teammatePhrase} to help you get oriented or work through something you're building.\n\n${WELCOME_KICKOFF_CTA}`; + return `${greeting} Welcome to Dreamforge. This is your private home base, and I'm here${teammatePhrase} to help you get oriented or work through something you're building.\n\n${WELCOME_KICKOFF_CTA}`; } - return `${greeting} Welcome to Buzz. This is your private home base, and we're here to help you get oriented or work through something you're building.\n\n${introNames}, introduce ${introTeammates.length === 1 ? "yourself" : "yourselves"} in a sentence or two — share what you're good at and when to bring you in. Don't start any work yet.`; + return `${greeting} Welcome to Dreamforge. This is your private home base, and we're here to help you get oriented or work through something you're building.\n\n${introNames}, introduce ${introTeammates.length === 1 ? "yourself" : "yourselves"} in a sentence or two — share what you're good at and when to bring you in. Don't start any work yet.`; } export function onlineWelcomeTeammates( diff --git a/desktop/src/features/profile/ui/AnimatedAvatarCapture.tsx b/desktop/src/features/profile/ui/AnimatedAvatarCapture.tsx index ca0c2d1ef3c..f80b6b1bb8b 100644 --- a/desktop/src/features/profile/ui/AnimatedAvatarCapture.tsx +++ b/desktop/src/features/profile/ui/AnimatedAvatarCapture.tsx @@ -378,7 +378,7 @@ export function AnimatedAvatarCapture({ } catch { releaseCamera(); setErrorMessage( - "Could not access the camera. Check Buzz's camera permission and try again.", + "Could not access the camera. Check Dreamforge's camera permission and try again.", ); setPhase("idle"); } diff --git a/desktop/src/features/profile/ui/NostrBindConsentDialog.tsx b/desktop/src/features/profile/ui/NostrBindConsentDialog.tsx index 976d51bfbbf..ef9b2a849b2 100644 --- a/desktop/src/features/profile/ui/NostrBindConsentDialog.tsx +++ b/desktop/src/features/profile/ui/NostrBindConsentDialog.tsx @@ -19,9 +19,10 @@ import { StartupWindowDragRegion } from "@/shared/ui/StartupWindowDragRegion"; import { writeTextToClipboard } from "@/shared/lib/clipboard"; const COPY_SUCCESS_MESSAGE = - "Signed response copied. Paste it into the Buzz admin console."; + "Signed response copied. Paste it into the Dreamforge admin console."; const PREVIEW_COPY_SUCCESS_MESSAGE = "Preview response copied."; -const COPY_FAILURE_MESSAGE = "Buzz couldn't access the clipboard. Try again."; +const COPY_FAILURE_MESSAGE = + "Dreamforge couldn't access the clipboard. Try again."; const EXPIRED_LINK_MESSAGE = "This binding link has expired. Request a new one from the requesting app."; const VERIFICATION_CODE_LENGTH = 6; @@ -298,7 +299,7 @@ export function NostrBindConsentDialog() { .catch((error) => { console.warn("get_identity for nostr bind failed:", error); setIdentity(null); - setError("Could not load the current Buzz identity."); + setError("Could not load the current Dreamforge identity."); }); }); @@ -652,7 +653,7 @@ export function NostrBindConsentDialog() {
Buzz {payload.returnMode === "browser_fragment_v1" ? "Continue in your browser" - : "Finish on the Buzz website"} + : "Finish on the Dreamforge website"} {payload.returnMode === "browser_fragment_v1" - ? "Buzz opened your browser to finish verification." - : "Copy the response below, then paste it into the Buzz website to finish verification."} + ? "Dreamforge opened your browser to finish verification." + : "Copy the response below, then paste it into the Dreamforge website to finish verification."} {error ? ( diff --git a/desktop/src/features/profile/ui/UserProfileAgentManagementRows.tsx b/desktop/src/features/profile/ui/UserProfileAgentManagementRows.tsx index 7b8a586224e..00b19b3b9ae 100644 --- a/desktop/src/features/profile/ui/UserProfileAgentManagementRows.tsx +++ b/desktop/src/features/profile/ui/UserProfileAgentManagementRows.tsx @@ -269,7 +269,7 @@ function AgentDeleteConfirmDialog({
  • {isProviderAgent - ? "Requests remote deletion; if it is online, Buzz first sends a shutdown command when possible. If the deployment cannot be reached through a channel, the remote process may keep running without local management." + ? "Requests remote deletion; if it is online, Dreamforge first sends a shutdown command when possible. If the deployment cannot be reached through a channel, the remote process may keep running without local management." : "Stops any local agent process before deleting the record"}
  • diff --git a/desktop/src/features/projects/lib/projectDetailAgentContext.ts b/desktop/src/features/projects/lib/projectDetailAgentContext.ts index 97142157007..d955e0fdc27 100644 --- a/desktop/src/features/projects/lib/projectDetailAgentContext.ts +++ b/desktop/src/features/projects/lib/projectDetailAgentContext.ts @@ -4,7 +4,7 @@ import { projectSelectionNoun, } from "./projectSelection.ts"; -const PROJECT_PAGE_CONTEXT_MARKER = "Current Buzz project page:"; +const PROJECT_PAGE_CONTEXT_MARKER = "Current Dreamforge project page:"; /** Marker for the repository set appended by the full Projects agent page. */ export const PROJECT_WORKSPACE_CONTEXT_MARKER = "Workspace repositories:"; const PROJECT_AGENT_CONTEXT_MARKERS = [ diff --git a/desktop/src/features/projects/lib/projectGitError.ts b/desktop/src/features/projects/lib/projectGitError.ts index 0d1377a0573..0adc09302a3 100644 --- a/desktop/src/features/projects/lib/projectGitError.ts +++ b/desktop/src/features/projects/lib/projectGitError.ts @@ -41,8 +41,8 @@ export function projectCloneErrorPresentation( return { title: "Repository access required", description: github - ? "This repository requires GitHub authentication. Buzz currently clones public GitHub repositories without credentials." - : "Buzz could not authenticate with this repository. Check your access and try again.", + ? "This repository requires GitHub authentication. Dreamforge currently clones public GitHub repositories without credentials." + : "Dreamforge could not authenticate with this repository. Check your access and try again.", }; } if (/\b404\b|repository not found|repository does not exist/.test(message)) { diff --git a/desktop/src/features/projects/lib/projectHomeTemplate.ts b/desktop/src/features/projects/lib/projectHomeTemplate.ts index bb6545466a3..c9c86870c5b 100644 --- a/desktop/src/features/projects/lib/projectHomeTemplate.ts +++ b/desktop/src/features/projects/lib/projectHomeTemplate.ts @@ -41,7 +41,7 @@ Everything about this project—decisions, tasks, code review, and releases—ha 1. **Pick up:** Find or create an issue, self-assign it, and post a one-line “picked up” message in the channel. 2. **Build:** Clone or reuse a checkout under \`REPOS/\`. Work on a branch, never the default branch. Follow the repository's configured commit and sign-off policy. 3. **Verify:** Run the fullest relevant test suite before calling anything done. -4. **Ship:** Open a review and post the returned Buzz link verbatim so it renders as a card. Mark the issue resolved when merged. +4. **Ship:** Open a review and post the returned Dreamforge link verbatim so it renders as a card. Mark the issue resolved when merged. 5. **Report:** @mention whoever delegated the work in the message that delivers the result or blocker—not in acknowledgements. ## Norms diff --git a/desktop/src/features/projects/lib/projectRepoAvailability.ts b/desktop/src/features/projects/lib/projectRepoAvailability.ts index d7911a0827f..da44233aa4a 100644 --- a/desktop/src/features/projects/lib/projectRepoAvailability.ts +++ b/desktop/src/features/projects/lib/projectRepoAvailability.ts @@ -19,12 +19,12 @@ const PROJECT_REPO_UNAVAILABLE_PRESENTATIONS: Record< > = { authentication: { description: - "Buzz could not authenticate with this repository. Check your access and try again.", + "Dreamforge could not authenticate with this repository. Check your access and try again.", title: "Repository access failed", }, missing: { description: - "The project announcement exists, but its git repository was not found on the Buzz relay.", + "The project announcement exists, but its git repository was not found on the Dreamforge relay.", title: "Repository not initialized", }, access: { @@ -39,7 +39,7 @@ const PROJECT_REPO_UNAVAILABLE_PRESENTATIONS: Record< }, network: { description: - "The Buzz git service could not be reached. Check your connection and try again.", + "The Dreamforge git service could not be reached. Check your connection and try again.", title: "Couldn’t reach repository", }, ref: { @@ -49,7 +49,7 @@ const PROJECT_REPO_UNAVAILABLE_PRESENTATIONS: Record< }, unknown: { description: - "Buzz could not load this repository. Try again or contact the project owner.", + "Dreamforge could not load this repository. Try again or contact the project owner.", title: "Repository unavailable", }, }; diff --git a/desktop/src/features/projects/ui/AgentContextPayloadPreview.tsx b/desktop/src/features/projects/ui/AgentContextPayloadPreview.tsx index 550cadd86aa..f7be76c6dba 100644 --- a/desktop/src/features/projects/ui/AgentContextPayloadPreview.tsx +++ b/desktop/src/features/projects/ui/AgentContextPayloadPreview.tsx @@ -53,8 +53,8 @@ export function AgentContextPayloadPreview({ >

    This exact text is appended to your message before it is signed and - sent. Quoted values are untrusted workspace metadata — Buzz does not - verify or rewrite them. + sent. Quoted values are untrusted workspace metadata — Dreamforge + does not verify or rewrite them.

               

    Clone this repository locally to explore its files, commits, and - contributors in Buzz. + contributors in Dreamforge.

    {externalOpenUrl ? (
    - Choose the highlight color used throughout Buzz. + Choose the highlight color used throughout Dreamforge.

    {result.matchesCurrentIdentity - ? "It restores your current Buzz identity." + ? "It restores your current Dreamforge identity." : "It restores a different identity than the one signed in here."}

    diff --git a/desktop/src/features/settings/ui/EncryptedBackupCreator.tsx b/desktop/src/features/settings/ui/EncryptedBackupCreator.tsx index fb20eb9c652..a9f85b72c37 100644 --- a/desktop/src/features/settings/ui/EncryptedBackupCreator.tsx +++ b/desktop/src/features/settings/ui/EncryptedBackupCreator.tsx @@ -277,8 +277,8 @@ export function EncryptedBackupCreator({ Create a key backup - You can close this window while Buzz finishes the backup in the - background. + You can close this window while Dreamforge finishes the backup in + the background.
    - Keep the file private and save its password somewhere safe — Buzz - cannot reset it. Once ready, the backup remains available to - download for 5 minutes. + Keep the file private and save its password somewhere safe — + Dreamforge cannot reset it. Once ready, the backup remains + available to download for 5 minutes.

    ) : null} diff --git a/desktop/src/features/settings/ui/HarnessesSettingsPanel.tsx b/desktop/src/features/settings/ui/HarnessesSettingsPanel.tsx index 2ce769e6462..c9b159b91ff 100644 --- a/desktop/src/features/settings/ui/HarnessesSettingsPanel.tsx +++ b/desktop/src/features/settings/ui/HarnessesSettingsPanel.tsx @@ -113,7 +113,7 @@ export function HarnessesSettingsPanel() { return ( - run("Connecting Buzz identity…", async () => { + run("Connecting Dreamforge identity…", async () => { const response = await invoke( "bind_builderlab_nostr_identity", ); @@ -174,7 +174,7 @@ export function HostedCommunitiesSettingsCard() { errorMessage( response.error, response.correlation_id, - "Could not connect the Buzz identity.", + "Could not connect the Dreamforge identity.", ), ); } @@ -192,7 +192,7 @@ export function HostedCommunitiesSettingsCard() { errorMessage( response.error, response.correlation_id, - "Could not unpair the Buzz identity.", + "Could not unpair the Dreamforge identity.", ), ); } @@ -230,7 +230,7 @@ export function HostedCommunitiesSettingsCard() { errorMessage( released.error, released.correlation_id, - "Could not release the previously connected Buzz identity.", + "Could not release the previously connected Dreamforge identity.", ), ); } @@ -243,11 +243,11 @@ export function HostedCommunitiesSettingsCard() { await loadAccount(); throw new Error( bound.error.code === "pubkey_already_bound" - ? "This device's Buzz identity is already reserved by another Builderlab account, so it can't be connected here. Sign in with that account, or transfer the identity there first." + ? "This device's Dreamforge identity is already reserved by another Builderlab account, so it can't be connected here. Sign in with that account, or transfer the identity there first." : errorMessage( bound.error, bound.correlation_id, - "Could not connect this device's Buzz identity.", + "Could not connect this device's Dreamforge identity.", ), ); } @@ -374,7 +374,7 @@ export function HostedCommunitiesSettingsCard() { errorMessage( availabilityResponse.error, availabilityResponse.correlation_id, - "That Buzz address is already taken.", + "That Dreamforge address is already taken.", ), ); } @@ -418,7 +418,7 @@ export function HostedCommunitiesSettingsCard() {
    {error ? ( @@ -439,8 +439,9 @@ export function HostedCommunitiesSettingsCard() { className="mt-2 max-w-2xl text-sm text-muted-foreground/70" data-settings-subcopy > - Authentication opens in your browser and returns securely to Buzz. - You can use every other part of the app without signing in. + Authentication opens in your browser and returns securely to + Dreamforge. You can use every other part of the app without signing + in.

    ) : identityMismatch ? ( @@ -507,16 +508,16 @@ export function HostedCommunitiesSettingsCard() {

    - This account is connected to a different Buzz identity + This account is connected to a different Dreamforge identity

    - Your Builderlab account owns communities under another Buzz - key, so connecting them here would join a relay this device - isn't a member of. Creating and connecting are paused - until the identities match. + Your Builderlab account owns communities under another + Dreamforge key, so connecting them here would join a relay + this device isn't a member of. Creating and connecting + are paused until the identities match.

    @@ -546,7 +547,7 @@ export function HostedCommunitiesSettingsCard() { ) : (
    - Buzz + Dreamforge identity connected {identity.npub ? ( {identity.npub} @@ -718,11 +719,11 @@ function UnpairIdentityButton({ - Unpair this Buzz identity? + Unpair this Dreamforge identity? - Your Builderlab account will no longer be connected to this Buzz - key. You can reconnect any key later, but community actions stay - unavailable until you do. + Your Builderlab account will no longer be connected to this + Dreamforge key. You can reconnect any key later, but community + actions stay unavailable until you do. @@ -916,8 +917,8 @@ function TransferOwnershipDialog({ Transfer ownership Transfer {communityName} to another person. You become a regular - member. The recipient needs a connected Buzz identity first, and - this can't be undone. + member. The recipient needs a connected Dreamforge identity first, + and this can't be undone.
    diff --git a/desktop/src/features/settings/ui/MobilePairingCard.tsx b/desktop/src/features/settings/ui/MobilePairingCard.tsx index 3a25f2edfaf..3977239a4ed 100644 --- a/desktop/src/features/settings/ui/MobilePairingCard.tsx +++ b/desktop/src/features/settings/ui/MobilePairingCard.tsx @@ -126,7 +126,7 @@ function PairingSteps({ step }: { step: PairingStep }) { className="mt-1 text-sm text-muted-foreground/70" data-settings-subcopy > - Open Buzz on your mobile device and scan the code shown here. + Open Dreamforge on your mobile device and scan the code shown here.

    @@ -397,9 +397,9 @@ export function MobilePairingCard({ title="Mobile" description={ <> - Connect the Buzz mobile app to this relay by scanning a QR code. The - connection is secured with end-to-end encryption and a verification - code. + Connect the Dreamforge mobile app to this relay by scanning a QR + code. The connection is secured with end-to-end encryption and a + verification code. } /> diff --git a/desktop/src/features/settings/ui/ProfileSettingsCard.tsx b/desktop/src/features/settings/ui/ProfileSettingsCard.tsx index 64af0f3410a..8530999d69a 100644 --- a/desktop/src/features/settings/ui/ProfileSettingsCard.tsx +++ b/desktop/src/features/settings/ui/ProfileSettingsCard.tsx @@ -486,7 +486,7 @@ export function ProfileSettingsCard({
    diff --git a/desktop/src/features/settings/ui/SendFeedbackDialog.tsx b/desktop/src/features/settings/ui/SendFeedbackDialog.tsx index fa81c34f3a2..3b8a547867b 100644 --- a/desktop/src/features/settings/ui/SendFeedbackDialog.tsx +++ b/desktop/src/features/settings/ui/SendFeedbackDialog.tsx @@ -167,8 +167,8 @@ export function SendFeedbackDialog({ className="pt-2 text-sm text-muted-foreground" data-testid="feedback-privacy-disclosure" > - Feedback is sent privately to this Buzz deployment and is not posted - to a channel. Attachments are uploaded before you send. + Feedback is sent privately to this Dreamforge deployment and is not + posted to a channel. Attachments are uploaded before you send.

    diff --git a/desktop/src/features/settings/ui/SettingsPanels.tsx b/desktop/src/features/settings/ui/SettingsPanels.tsx index 6b00fc8f74c..45af93ef94d 100644 --- a/desktop/src/features/settings/ui/SettingsPanels.tsx +++ b/desktop/src/features/settings/ui/SettingsPanels.tsx @@ -576,7 +576,7 @@ function ThemeSettingsCard() { }} /> {/* Bottom fade — hidden while the accent picker is visible so its - near-white gradient (Buzz light) can't mask the swatches below it + near-white gradient (Dreamforge light) can't mask the swatches below it (the "white bar"). Kept only when the picker is hidden. */} {accentPickerHidden ? (
    @@ -698,7 +698,7 @@ function ThemeSettingsCard() { className="text-sm font-normal text-muted-foreground/70" data-settings-subcopy > - Choose the colors used throughout Buzz. + Choose the colors used throughout Dreamforge.

    - {owner === "terminal" ? "Buzz Term mode" : "Buzz mode"} + {owner === "terminal" ? "Dreamforge Term mode" : "Dreamforge mode"}
    ); diff --git a/desktop/src/features/terminal/terminalClient.ts b/desktop/src/features/terminal/terminalClient.ts index de51f596535..14d93150501 100644 --- a/desktop/src/features/terminal/terminalClient.ts +++ b/desktop/src/features/terminal/terminalClient.ts @@ -58,7 +58,8 @@ export class TerminalConnection { onMessage: (message: Exclude) => void, onFrame: (delivery: TerminalDelivery) => void, ): Promise { - if (!isTauri()) throw new Error("terminal sessions require Buzz Desktop"); + if (!isTauri()) + throw new Error("terminal sessions require Dreamforge Desktop"); let connection: TerminalConnection | null = null; const pending: TerminalMessage[] = []; diff --git a/desktop/src/shared/constants/brand.ts b/desktop/src/shared/constants/brand.ts new file mode 100644 index 00000000000..947284be35b --- /dev/null +++ b/desktop/src/shared/constants/brand.ts @@ -0,0 +1,37 @@ +// The product name, in one place. +// +// This fork ships as Dreamforge. Upstream is `block/buzz`, and we track it, so +// the rename is deliberately confined to what a person actually sees. Nothing +// here changes an identifier that code or the protocol depends on: +// +// RENAMED window title, app name, and user-visible copy +// UNCHANGED crate names (buzz-*), so upstream `use` paths keep resolving +// UNCHANGED BUZZ_* environment variables, so upstream code that reads a new +// variable still finds the one we set +// UNCHANGED on-the-wire tags (buzz-channel, buzz-protect, buzz-visibility) +// and event kinds, so we stay interoperable with other Buzz +// relays and clients +// +// Those three exclusions are not cosmetic caution. Renaming an env var makes +// upstream code read a variable nobody sets — it compiles, runs, and silently +// takes a default. Renaming a wire tag diverges the protocol with no error at +// all. Both fail quietly, which is the failure mode this project exists to +// remove. +// +// To rename again later: change PRODUCT_NAME here, then sweep the remaining +// prose mentions. Sentences that mention the product by name hold the literal +// word rather than an interpolation — turning every sentence into a template +// literal buys nothing and makes each one a merge conflict against upstream. +// The identity surfaces below are what must stay centralized. + +/** The product name shown to people. */ +export const PRODUCT_NAME = "Dreamforge"; + +/** Window title and anywhere the app names itself standalone. */ +export const APP_TITLE = PRODUCT_NAME; + +/** Built-in terminal panel, e.g. the "Open …" control in a channel header. */ +export const TERMINAL_LABEL = `${PRODUCT_NAME} Term`; + +/** Shared compute pool, shown when choosing where an agent runs. */ +export const SHARED_COMPUTE_LABEL = `${PRODUCT_NAME} shared compute`; diff --git a/desktop/src/shared/lib/linkPreview.ts b/desktop/src/shared/lib/linkPreview.ts index aa66784abf6..b537b85d3f7 100644 --- a/desktop/src/shared/lib/linkPreview.ts +++ b/desktop/src/shared/lib/linkPreview.ts @@ -319,7 +319,7 @@ function parseBuzzEntityPreview(href: string): SupportedLinkPreview | null { return { kind: "buzz-pull-request", href: buildPullRequestLink(link), - provider: "Buzz", + provider: "Dreamforge", title, typeLabel: "Review", }; @@ -328,7 +328,7 @@ function parseBuzzEntityPreview(href: string): SupportedLinkPreview | null { return { kind: "buzz-issue", href: buildIssueLink(link), - provider: "Buzz", + provider: "Dreamforge", title, typeLabel: "Task", }; @@ -337,7 +337,7 @@ function parseBuzzEntityPreview(href: string): SupportedLinkPreview | null { return { kind: "buzz-project", href: buildProjectLink(link), - provider: "Buzz", + provider: "Dreamforge", title, typeLabel: "project", }; @@ -345,7 +345,7 @@ function parseBuzzEntityPreview(href: string): SupportedLinkPreview | null { return { kind: "buzz-repository", href: buildRepoLink(link), - provider: "Buzz", + provider: "Dreamforge", title, typeLabel: "repo", }; @@ -387,7 +387,7 @@ function parseBuzzGitLink( return { kind: "buzz-repository", href: buildRepoLink({ owner, dtag: repo }), - provider: "Buzz", + provider: "Dreamforge", title: repo, typeLabel: "repo", }; diff --git a/desktop/src/shared/lib/localStorageQuota.ts b/desktop/src/shared/lib/localStorageQuota.ts index 68f6b50aa39..ecc90be891b 100644 --- a/desktop/src/shared/lib/localStorageQuota.ts +++ b/desktop/src/shared/lib/localStorageQuota.ts @@ -133,7 +133,7 @@ function notifyStorageFull(): void { .then(({ toast }) => { toast.error("Local storage is full", { description: - "Buzz could not save some local data — read positions may not persist across restarts.", + "Dreamforge could not save some local data — read positions may not persist across restarts.", }); }) .catch(() => {}); diff --git a/desktop/src/shared/ui/buzz-logo/BuzzLogoAnimation.tsx b/desktop/src/shared/ui/buzz-logo/BuzzLogoAnimation.tsx index 3f8a8b8743c..5f95dc3ede4 100644 --- a/desktop/src/shared/ui/buzz-logo/BuzzLogoAnimation.tsx +++ b/desktop/src/shared/ui/buzz-logo/BuzzLogoAnimation.tsx @@ -622,7 +622,7 @@ function RestWindowFade({ } export default function BuzzLogoAnimation({ - ariaLabel = "Buzz logo animation", + ariaLabel = "Dreamforge logo animation", className = "", fullScreen = true, loop = false, diff --git a/desktop/src/shared/ui/buzz-logo/FuzzyLogo.tsx b/desktop/src/shared/ui/buzz-logo/FuzzyLogo.tsx index 845033aade5..011894dea2d 100644 --- a/desktop/src/shared/ui/buzz-logo/FuzzyLogo.tsx +++ b/desktop/src/shared/ui/buzz-logo/FuzzyLogo.tsx @@ -25,7 +25,7 @@ export type FuzzyLogoProps = { export function FuzzyLogo({ fuzz = true, className, - ariaLabel = "Buzz logo", + ariaLabel = "Dreamforge logo", loop = false, loopRestSeconds = 0, pulse = true, diff --git a/desktop/src/testing/e2eBridge.ts b/desktop/src/testing/e2eBridge.ts index 9c12ebef4fe..8facc696b3e 100644 --- a/desktop/src/testing/e2eBridge.ts +++ b/desktop/src/testing/e2eBridge.ts @@ -2289,7 +2289,7 @@ function buildMockConfigSurface(pubkey: string): { const buzzAgentSurface = { ...gooseSurface, runtimeId: "buzz-agent", - runtimeLabel: "Buzz Agent", + runtimeLabel: "Dreamforge Agent", advanced: [], extensions: [], sources: { @@ -2727,7 +2727,7 @@ const mockChannels: MockChannel[] = [ name: "buzz", channel_type: "stream", visibility: "open", - description: "Project home for the Buzz community platform.", + description: "Project home for the Dreamforge community platform.", topic: null, purpose: null, last_message_at: null, @@ -5906,7 +5906,7 @@ const MOCK_PROJECT_SEEDS = [ dtag: "buzz", name: "buzz", description: - "Relay, desktop, and mobile clients for the Buzz community platform.", + "Relay, desktop, and mobile clients for the Dreamforge community platform.", cloneUrl: `${DEFAULT_RELAY_HTTP_URL}/git/${MOCK_IDENTITY_PUBKEY}/buzz`, webUrl: null, owner: MOCK_IDENTITY_PUBKEY, @@ -6121,7 +6121,7 @@ function buildMockProjectEvents(): RelayEvent[] { [ ["d", "buzz"], ["name", "buzz"], - ["description", "The complete Buzz community platform."], + ["description", "The complete Dreamforge community platform."], ["a", `${KIND_REPO_ANNOUNCEMENT}:${projectOwner}:buzz`], ["a", `${KIND_REPO_ANNOUNCEMENT}:${ALICE_PUBKEY}:relay-tools`], [ @@ -8305,14 +8305,14 @@ async function handleDiscoverAcpRuntimes( }, { id: "buzz-agent", - label: "Buzz Agent", + label: "Dreamforge Agent", avatar_url: "", availability: "available", command: "buzz-agent", binary_path: "/usr/local/bin/buzz-agent", default_args: [], mcp_command: "buzz-dev-mcp", - install_hint: "Ships with the Buzz desktop app.", + install_hint: "Ships with the Dreamforge desktop app.", install_instructions_url: "https://github.com/block/buzz", can_auto_install: false, requires_external_cli: false, @@ -9497,7 +9497,7 @@ async function handleStartManagedAgent( mockMeshState.models.some((model) => model.id === modelId)); if (!hasLiveTarget) { throw new Error( - "Buzz shared compute cannot start because no live member is serving this model.", + "Dreamforge shared compute cannot start because no live member is serving this model.", ); } } @@ -12501,7 +12501,7 @@ export function maybeInstallE2eTauriMocks() { kind: "blob", size: 33120, preview_content: - "// Smart HTTP git transport\n// Handles upload-pack and receive-pack for Buzz git repos.\n", + "// Smart HTTP git transport\n// Handles upload-pack and receive-pack for Dreamforge git repos.\n", }, ], }; @@ -13615,7 +13615,7 @@ export function maybeInstallE2eTauriMocks() { } if (mockMeshState.models.length === 0) { throw new Error( - "no Buzz shared compute serving members are available", + "no Dreamforge shared compute serving members are available", ); } } diff --git a/desktop/tsconfig.node.tsbuildinfo b/desktop/tsconfig.node.tsbuildinfo new file mode 100644 index 00000000000..2563c196b48 --- /dev/null +++ b/desktop/tsconfig.node.tsbuildinfo @@ -0,0 +1 @@ +{"fileNames":["../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2015.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2016.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2017.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2018.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2019.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2020.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2021.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2022.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2023.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2024.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2025.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.dom.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.dom.iterable.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.dom.asynciterable.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.scripthost.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2015.core.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2015.collection.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2015.generator.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2015.promise.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2016.intl.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2017.date.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2017.object.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2017.string.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2017.intl.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2018.intl.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2018.promise.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2018.regexp.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2019.array.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2019.object.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2019.string.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2019.symbol.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2019.intl.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2020.bigint.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2020.date.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2020.promise.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2020.string.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2020.intl.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2020.number.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2021.promise.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2021.string.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2021.weakref.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2021.intl.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2022.array.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2022.error.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2022.intl.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2022.object.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2022.string.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2022.regexp.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2023.array.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2023.collection.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2023.intl.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2024.collection.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2024.object.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2024.promise.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2024.regexp.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2024.string.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2025.collection.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2025.float16.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2025.intl.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2025.iterator.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2025.promise.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2025.regexp.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.decorators.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.decorators.legacy.d.ts","../node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2025.full.d.ts","../../../node_modules/@types/node/compatibility/disposable.d.ts","../../../node_modules/@types/node/compatibility/indexable.d.ts","../../../node_modules/@types/node/compatibility/iterators.d.ts","../../../node_modules/@types/node/compatibility/index.d.ts","../../../node_modules/@types/node/globals.typedarray.d.ts","../../../node_modules/@types/node/buffer.buffer.d.ts","../../../node_modules/undici-types/header.d.ts","../../../node_modules/undici-types/readable.d.ts","../../../node_modules/undici-types/file.d.ts","../../../node_modules/undici-types/fetch.d.ts","../../../node_modules/undici-types/formdata.d.ts","../../../node_modules/undici-types/connector.d.ts","../../../node_modules/undici-types/client.d.ts","../../../node_modules/undici-types/errors.d.ts","../../../node_modules/undici-types/dispatcher.d.ts","../../../node_modules/undici-types/global-dispatcher.d.ts","../../../node_modules/undici-types/global-origin.d.ts","../../../node_modules/undici-types/pool-stats.d.ts","../../../node_modules/undici-types/pool.d.ts","../../../node_modules/undici-types/handlers.d.ts","../../../node_modules/undici-types/balanced-pool.d.ts","../../../node_modules/undici-types/agent.d.ts","../../../node_modules/undici-types/mock-interceptor.d.ts","../../../node_modules/undici-types/mock-agent.d.ts","../../../node_modules/undici-types/mock-client.d.ts","../../../node_modules/undici-types/mock-pool.d.ts","../../../node_modules/undici-types/mock-errors.d.ts","../../../node_modules/undici-types/proxy-agent.d.ts","../../../node_modules/undici-types/env-http-proxy-agent.d.ts","../../../node_modules/undici-types/retry-handler.d.ts","../../../node_modules/undici-types/retry-agent.d.ts","../../../node_modules/undici-types/api.d.ts","../../../node_modules/undici-types/interceptors.d.ts","../../../node_modules/undici-types/util.d.ts","../../../node_modules/undici-types/cookies.d.ts","../../../node_modules/undici-types/patch.d.ts","../../../node_modules/undici-types/websocket.d.ts","../../../node_modules/undici-types/eventsource.d.ts","../../../node_modules/undici-types/filereader.d.ts","../../../node_modules/undici-types/diagnostics-channel.d.ts","../../../node_modules/undici-types/content-type.d.ts","../../../node_modules/undici-types/cache.d.ts","../../../node_modules/undici-types/index.d.ts","../../../node_modules/@types/node/globals.d.ts","../../../node_modules/@types/node/assert.d.ts","../../../node_modules/@types/node/assert/strict.d.ts","../../../node_modules/@types/node/async_hooks.d.ts","../../../node_modules/@types/node/buffer.d.ts","../../../node_modules/@types/node/child_process.d.ts","../../../node_modules/@types/node/cluster.d.ts","../../../node_modules/@types/node/console.d.ts","../../../node_modules/@types/node/constants.d.ts","../../../node_modules/@types/node/crypto.d.ts","../../../node_modules/@types/node/dgram.d.ts","../../../node_modules/@types/node/diagnostics_channel.d.ts","../../../node_modules/@types/node/dns.d.ts","../../../node_modules/@types/node/dns/promises.d.ts","../../../node_modules/@types/node/domain.d.ts","../../../node_modules/@types/node/dom-events.d.ts","../../../node_modules/@types/node/events.d.ts","../../../node_modules/@types/node/fs.d.ts","../../../node_modules/@types/node/fs/promises.d.ts","../../../node_modules/@types/node/http.d.ts","../../../node_modules/@types/node/http2.d.ts","../../../node_modules/@types/node/https.d.ts","../../../node_modules/@types/node/inspector.d.ts","../../../node_modules/@types/node/module.d.ts","../../../node_modules/@types/node/net.d.ts","../../../node_modules/@types/node/os.d.ts","../../../node_modules/@types/node/path.d.ts","../../../node_modules/@types/node/perf_hooks.d.ts","../../../node_modules/@types/node/process.d.ts","../../../node_modules/@types/node/punycode.d.ts","../../../node_modules/@types/node/querystring.d.ts","../../../node_modules/@types/node/readline.d.ts","../../../node_modules/@types/node/readline/promises.d.ts","../../../node_modules/@types/node/repl.d.ts","../../../node_modules/@types/node/sea.d.ts","../../../node_modules/@types/node/sqlite.d.ts","../../../node_modules/@types/node/stream.d.ts","../../../node_modules/@types/node/stream/promises.d.ts","../../../node_modules/@types/node/stream/consumers.d.ts","../../../node_modules/@types/node/stream/web.d.ts","../../../node_modules/@types/node/string_decoder.d.ts","../../../node_modules/@types/node/test.d.ts","../../../node_modules/@types/node/timers.d.ts","../../../node_modules/@types/node/timers/promises.d.ts","../../../node_modules/@types/node/tls.d.ts","../../../node_modules/@types/node/trace_events.d.ts","../../../node_modules/@types/node/tty.d.ts","../../../node_modules/@types/node/url.d.ts","../../../node_modules/@types/node/util.d.ts","../../../node_modules/@types/node/v8.d.ts","../../../node_modules/@types/node/vm.d.ts","../../../node_modules/@types/node/wasi.d.ts","../../../node_modules/@types/node/worker_threads.d.ts","../../../node_modules/@types/node/zlib.d.ts","../../../node_modules/@types/node/index.d.ts","../node_modules/.pnpm/vite@8.0.16_@types+node@25.6.0_jiti@2.7.0_yaml@2.9.0/node_modules/vite/types/hmrpayload.d.ts","../node_modules/.pnpm/vite@8.0.16_@types+node@25.6.0_jiti@2.7.0_yaml@2.9.0/node_modules/vite/dist/node/chunks/modulerunnertransport.d.ts","../node_modules/.pnpm/vite@8.0.16_@types+node@25.6.0_jiti@2.7.0_yaml@2.9.0/node_modules/vite/types/customevent.d.ts","../node_modules/.pnpm/rolldown@1.0.3/node_modules/rolldown/dist/shared/logging-bsnejils.d.mts","../node_modules/.pnpm/@oxc-project+types@0.133.0/node_modules/@oxc-project/types/types.d.ts","../node_modules/.pnpm/rolldown@1.0.3/node_modules/rolldown/dist/shared/binding-bacztfmx.d.mts","../node_modules/.pnpm/@rolldown+pluginutils@1.0.1/node_modules/@rolldown/pluginutils/dist/filter/index.d.mts","../node_modules/.pnpm/@rolldown+pluginutils@1.0.1/node_modules/@rolldown/pluginutils/dist/index.d.mts","../node_modules/.pnpm/rolldown@1.0.3/node_modules/rolldown/dist/shared/define-config-3bx_x2am.d.mts","../node_modules/.pnpm/rolldown@1.0.3/node_modules/rolldown/dist/index.d.mts","../node_modules/.pnpm/rolldown@1.0.3/node_modules/rolldown/dist/parse-ast-index.d.mts","../node_modules/.pnpm/vite@8.0.16_@types+node@25.6.0_jiti@2.7.0_yaml@2.9.0/node_modules/vite/types/internal/rolluptypecompat.d.ts","../node_modules/.pnpm/rolldown@1.0.3/node_modules/rolldown/dist/shared/constructors-cbnat434.d.mts","../node_modules/.pnpm/rolldown@1.0.3/node_modules/rolldown/dist/plugins-index.d.mts","../node_modules/.pnpm/rolldown@1.0.3/node_modules/rolldown/dist/shared/transform-7xcuvrpl.d.mts","../node_modules/.pnpm/rolldown@1.0.3/node_modules/rolldown/dist/utils-index.d.mts","../node_modules/.pnpm/vite@8.0.16_@types+node@25.6.0_jiti@2.7.0_yaml@2.9.0/node_modules/vite/types/hot.d.ts","../node_modules/.pnpm/vite@8.0.16_@types+node@25.6.0_jiti@2.7.0_yaml@2.9.0/node_modules/vite/dist/node/module-runner.d.ts","../node_modules/.pnpm/vite@8.0.16_@types+node@25.6.0_jiti@2.7.0_yaml@2.9.0/node_modules/vite/types/internal/esbuildoptions.d.ts","../node_modules/.pnpm/vite@8.0.16_@types+node@25.6.0_jiti@2.7.0_yaml@2.9.0/node_modules/vite/types/metadata.d.ts","../node_modules/.pnpm/vite@8.0.16_@types+node@25.6.0_jiti@2.7.0_yaml@2.9.0/node_modules/vite/types/internal/terseroptions.d.ts","../node_modules/.pnpm/source-map-js@1.2.1/node_modules/source-map-js/source-map.d.ts","../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/previous-map.d.ts","../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/input.d.ts","../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/css-syntax-error.d.ts","../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/declaration.d.ts","../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/root.d.ts","../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/warning.d.ts","../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/lazy-result.d.ts","../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/no-work-result.d.ts","../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/processor.d.ts","../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/result.d.ts","../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/document.d.ts","../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/rule.d.ts","../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/node.d.ts","../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/comment.d.ts","../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/container.d.ts","../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/at-rule.d.ts","../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/list.d.ts","../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/postcss.d.ts","../node_modules/.pnpm/postcss@8.5.19/node_modules/postcss/lib/postcss.d.mts","../node_modules/.pnpm/lightningcss@1.32.0/node_modules/lightningcss/node/ast.d.ts","../node_modules/.pnpm/lightningcss@1.32.0/node_modules/lightningcss/node/targets.d.ts","../node_modules/.pnpm/lightningcss@1.32.0/node_modules/lightningcss/node/index.d.ts","../node_modules/.pnpm/vite@8.0.16_@types+node@25.6.0_jiti@2.7.0_yaml@2.9.0/node_modules/vite/types/internal/lightningcssoptions.d.ts","../node_modules/.pnpm/vite@8.0.16_@types+node@25.6.0_jiti@2.7.0_yaml@2.9.0/node_modules/vite/types/internal/csspreprocessoroptions.d.ts","../node_modules/.pnpm/rolldown@1.0.3/node_modules/rolldown/dist/filter-index.d.mts","../node_modules/.pnpm/vite@8.0.16_@types+node@25.6.0_jiti@2.7.0_yaml@2.9.0/node_modules/vite/types/importglob.d.ts","../node_modules/.pnpm/vite@8.0.16_@types+node@25.6.0_jiti@2.7.0_yaml@2.9.0/node_modules/vite/dist/node/index.d.ts","../node_modules/.pnpm/@vitejs+plugin-react@6.0.5__a5f721d038a14cf56b9162f61c68f638/node_modules/@vitejs/plugin-react/types/optionaltypes.d.ts","../node_modules/.pnpm/@vitejs+plugin-react@6.0.5__a5f721d038a14cf56b9162f61c68f638/node_modules/@vitejs/plugin-react/dist/index.d.ts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/json-schema.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/standard-schema.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/registries.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/to-json-schema.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/util.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/versions.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/schemas.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/checks.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/errors.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/core.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/parse.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/regexes.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/ar.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/az.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/be.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/bg.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/ca.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/cs.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/da.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/de.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/el.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/en.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/eo.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/es.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/fa.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/fi.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/fr.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/fr-ca.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/he.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/hr.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/hu.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/hy.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/id.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/is.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/it.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/ja.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/ka.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/kh.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/km.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/ko.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/lt.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/mk.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/ms.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/nl.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/no.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/ota.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/ps.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/pl.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/pt.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/ro.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/ru.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/sl.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/sv.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/ta.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/th.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/tr.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/ua.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/uk.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/ur.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/uz.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/vi.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/zh-cn.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/zh-tw.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/yo.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/locales/index.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/doc.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/api.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/json-schema-processors.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/json-schema-generator.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/index.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/errors.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/parse.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/schemas.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/checks.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/compat.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/from-json-schema.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/iso.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/coerce.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.d.cts","../node_modules/.pnpm/zod@4.4.3/node_modules/zod/index.d.cts","../node_modules/.pnpm/@tanstack+history@1.162.0/node_modules/@tanstack/history/dist/esm/index.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/fileroute.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/utils.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/link.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/routeinfo.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/not-found.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/redirect.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/matches.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/root.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/routerprovider.d.ts","../node_modules/.pnpm/seroval@1.5.4/node_modules/seroval/dist/types/core/compat.d.ts","../node_modules/.pnpm/seroval@1.5.4/node_modules/seroval/dist/types/core/symbols.d.ts","../node_modules/.pnpm/seroval@1.5.4/node_modules/seroval/dist/types/core/constants.d.ts","../node_modules/.pnpm/seroval@1.5.4/node_modules/seroval/dist/types/core/special-reference.d.ts","../node_modules/.pnpm/seroval@1.5.4/node_modules/seroval/dist/types/core/types.d.ts","../node_modules/.pnpm/seroval@1.5.4/node_modules/seroval/dist/types/core/context/deserializer.d.ts","../node_modules/.pnpm/seroval@1.5.4/node_modules/seroval/dist/types/core/context/serializer.d.ts","../node_modules/.pnpm/seroval@1.5.4/node_modules/seroval/dist/types/core/context/parser.d.ts","../node_modules/.pnpm/seroval@1.5.4/node_modules/seroval/dist/types/core/context/sync-parser.d.ts","../node_modules/.pnpm/seroval@1.5.4/node_modules/seroval/dist/types/core/plugin.d.ts","../node_modules/.pnpm/seroval@1.5.4/node_modules/seroval/dist/types/core/context/async-parser.d.ts","../node_modules/.pnpm/seroval@1.5.4/node_modules/seroval/dist/types/core/cross/index.d.ts","../node_modules/.pnpm/seroval@1.5.4/node_modules/seroval/dist/types/core/errors.d.ts","../node_modules/.pnpm/seroval@1.5.4/node_modules/seroval/dist/types/core/keys.d.ts","../node_modules/.pnpm/seroval@1.5.4/node_modules/seroval/dist/types/core/opaque-reference.d.ts","../node_modules/.pnpm/seroval@1.5.4/node_modules/seroval/dist/types/core/reference.d.ts","../node_modules/.pnpm/seroval@1.5.4/node_modules/seroval/dist/types/core/serializer.d.ts","../node_modules/.pnpm/seroval@1.5.4/node_modules/seroval/dist/types/core/stream.d.ts","../node_modules/.pnpm/seroval@1.5.4/node_modules/seroval/dist/types/core/tree/index.d.ts","../node_modules/.pnpm/seroval@1.5.4/node_modules/seroval/dist/types/index.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/ssr/serializer/rawstream.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/ssr/serializer/transformer.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/route.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/validators.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/location.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/load-matches.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/lru-cache.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/new-process-route-tree.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/searchparams.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/manifest.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/stores.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/router.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/global.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/defer.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/invariant.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/path.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/qss.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/config.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/searchmiddleware.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/structuralsharing.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/useroutecontext.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/usesearch.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/useparams.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/usenavigate.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/useloaderdeps.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/useloaderdata.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/scroll-restoration.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/typeprimitives.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/ssr/serializer/seroval-plugins.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/rewrite.d.ts","../node_modules/.pnpm/@tanstack+router-core@1.171.5/node_modules/@tanstack/router-core/dist/esm/index.d.ts","../node_modules/.pnpm/@tanstack+router-plugin@1.1_846640ef0785c355529528b566e7c8f1/node_modules/@tanstack/router-plugin/dist/esm/core/constants.d.ts","../node_modules/.pnpm/@tanstack+virtual-file-routes@1.162.0/node_modules/@tanstack/virtual-file-routes/dist/esm/types.d.ts","../node_modules/.pnpm/@tanstack+virtual-file-routes@1.162.0/node_modules/@tanstack/virtual-file-routes/dist/esm/api.d.ts","../node_modules/.pnpm/@tanstack+virtual-file-routes@1.162.0/node_modules/@tanstack/virtual-file-routes/dist/esm/defineconfig.d.ts","../node_modules/.pnpm/@tanstack+virtual-file-routes@1.162.0/node_modules/@tanstack/virtual-file-routes/dist/esm/index.d.ts","../node_modules/.pnpm/@tanstack+router-generator@1.167.9/node_modules/@tanstack/router-generator/dist/esm/types.d.ts","../node_modules/.pnpm/@tanstack+router-generator@1.167.9/node_modules/@tanstack/router-generator/dist/esm/template.d.ts","../node_modules/.pnpm/@tanstack+router-generator@1.167.9/node_modules/@tanstack/router-generator/dist/esm/generator.d.ts","../node_modules/.pnpm/@tanstack+router-generator@1.167.9/node_modules/@tanstack/router-generator/dist/esm/plugin/types.d.ts","../node_modules/.pnpm/@tanstack+router-generator@1.167.9/node_modules/@tanstack/router-generator/dist/esm/config.d.ts","../node_modules/.pnpm/@tanstack+router-generator@1.167.9/node_modules/@tanstack/router-generator/dist/esm/utils.d.ts","../node_modules/.pnpm/@tanstack+router-generator@1.167.9/node_modules/@tanstack/router-generator/dist/esm/filesystem/physical/getroutenodes.d.ts","../node_modules/.pnpm/@tanstack+router-generator@1.167.9/node_modules/@tanstack/router-generator/dist/esm/filesystem/virtual/getroutenodes.d.ts","../node_modules/.pnpm/@tanstack+router-generator@1.167.9/node_modules/@tanstack/router-generator/dist/esm/filesystem/physical/rootpathid.d.ts","../node_modules/.pnpm/@tanstack+router-generator@1.167.9/node_modules/@tanstack/router-generator/dist/esm/transform/types.d.ts","../node_modules/.pnpm/@tanstack+router-generator@1.167.9/node_modules/@tanstack/router-generator/dist/esm/index.d.ts","../node_modules/.pnpm/@tanstack+router-plugin@1.1_846640ef0785c355529528b566e7c8f1/node_modules/@tanstack/router-plugin/dist/esm/core/config.d.ts","../node_modules/.pnpm/@tanstack+router-plugin@1.1_846640ef0785c355529528b566e7c8f1/node_modules/@tanstack/router-plugin/dist/esm/core/router-plugin-context.d.ts","../node_modules/.pnpm/@tanstack+router-plugin@1.1_846640ef0785c355529528b566e7c8f1/node_modules/@tanstack/router-plugin/dist/esm/vite.d.ts","./vite.config.ts"],"fileIdsList":[[88,130,152,229,231,391],[88,130],[88,130,187],[88,130,343,353],[88,130,353],[88,130,344,345,353],[88,130,313,314,315,316,317,318,319,320,321,342,343,344,345,346,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371],[88,130,312,314,316,346,353],[88,130,319,344,346,353],[88,130,312,345],[88,130,314,316,344,353],[88,130,348],[88,130,316,353],[88,130,315,353],[88,130,313,314,315,316,317,318,319,320,321,343,345,346,353],[88,130,313,314,315,344,353],[88,130,312,314,315,316,318,319,321,343,344,345,346,347,348,349,350,351,352],[88,130,315,316,346,353],[88,130,346,353],[88,130,314,315,344],[88,130,345],[88,130,341],[88,130,341,342],[88,130,314,341,342,344,353],[88,130,316,318,319,344,346,353],[88,130,314],[88,130,314,315,316,318,353,363,364],[88,130,314,316,353],[88,130,344],[88,130,311,377,381],[88,130,378,382],[88,130,377,378,382,384],[88,130,378,379,382],[88,130,378,380,381,382,383,384,385,386,387],[88,130,378,380],[88,130,382],[88,130,311,372,373,377,388],[88,130,388],[88,130,229,377,388,389,390],[88,130,374],[88,130,374,375,376],[88,130,229,230],[88,130,222,223],[88,130,217],[88,130,215,217],[88,130,206,214,215,216,218,220],[88,130,204],[88,130,207,212,217,220],[88,130,203,220],[88,130,207,208,211,212,213,220],[88,130,207,208,209,211,212,220],[88,130,204,205,206,207,208,212,213,214,216,217,218,220],[88,130,220],[88,130,202,204,205,206,207,208,209,211,212,213,214,215,216,217,218,219],[88,130,202,220],[88,130,207,209,210,212,213,220],[88,130,211,220],[88,130,212,213,217,220],[88,130,205,215],[88,130,189],[88,130,184,186,189],[88,130,185,186],[88,130,186,189,193],[88,130,185],[88,130,186,189],[88,130,184,185,186,188],[88,130,184,186],[88,130,185,186,195],[88,130,323],[88,130,326,329,331],[88,130,324,326,331],[88,130,325,326,331],[88,130,326,327,328,330,332],[88,130,326],[88,130,331],[88,130,324,325],[88,130,322,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340],[88,130,181],[88,130,142,143,145,146,147,150,162,170,173,179,180,181,182,183,190,191,192,194,196,198,199,200,201,221,225,226,227,228,229],[88,130,181,182,183,197],[88,130,183],[88,130,224],[88,130,190,200,229],[88,130,190,229],[88,130,310],[88,130,301],[88,130,301,304],[88,130,236,296,299,301,302,303,304,305,306,307,308,309],[88,130,232,234,304],[88,130,301,302],[88,130,233,301,303],[88,130,234,236,238,239,240,241],[88,130,236,238,240,241],[88,130,236,238,240],[88,130,233,236,238,239,241],[88,130,232,234,235,236,237,238,239,240,241,242,243,296,297,298,299,300],[88,130,232,234,235,238],[88,130,234,235,238],[88,130,238,241],[88,130,232,233,235,236,237,239,240,241],[88,130,232,233,234,238,301],[88,130,238,239,240,241],[88,130,240],[88,130,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295],[88,127,130],[88,129,130],[130],[88,130,135,165],[88,130,131,136,142,143,150,162,173],[88,130,131,132,142,150],[83,84,85,88,130],[88,130,133,174],[88,130,134,135,143,151],[88,130,135,162,170],[88,130,136,138,142,150],[88,129,130,137],[88,130,138,139],[88,130,142],[88,130,140,142],[88,129,130,142],[88,130,142,143,144,162,173],[88,130,142,143,144,157,162,165],[88,125,130,178],[88,125,130,138,142,145,150,162,173],[88,130,142,143,145,146,150,162,170,173],[88,130,145,147,162,170,173],[86,87,88,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179],[88,130,142,148],[88,130,149,173],[88,130,138,142,150,162],[88,130,151],[88,130,152],[88,129,130,153],[88,127,128,129,130,131,132,133,134,135,136,137,138,139,140,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179],[88,130,155],[88,130,156],[88,130,142,157,158],[88,130,157,159,174,176],[88,130,142,162,163,165],[88,130,162,164],[88,130,162,163],[88,130,165],[88,130,166],[88,127,130,162],[88,130,142,168,169],[88,130,168,169],[88,130,135,150,162,170],[88,130,171],[88,130,150,172],[88,130,145,156,173],[88,130,135,174],[88,130,162,175],[88,130,149,176],[88,130,177],[88,130,135,142,144,153,162,173,176,178],[88,130,162,179],[88,97,101,130,173],[88,97,130,162,173],[88,92,130],[88,94,97,130,170,173],[88,130,150,170],[88,130,180],[88,92,130,180],[88,94,97,130,150,173],[88,89,90,93,96,130,142,162,173],[88,97,104,130],[88,89,95,130],[88,97,118,119,130],[88,93,97,130,165,173,180],[88,118,130,180],[88,91,92,130,180],[88,97,130],[88,91,92,93,94,95,96,97,98,99,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,119,120,121,122,123,124,130],[88,97,112,130],[88,97,104,105,130],[88,95,97,105,106,130],[88,96,130],[88,89,92,97,130],[88,97,101,105,106,130],[88,101,130],[88,95,97,100,130,173],[88,89,94,97,104,130],[88,130,162],[88,92,97,118,130,178,180]],"fileInfos":[{"version":"bcd24271a113971ba9eb71ff8cb01bc6b0f872a85c23fdbe5d93065b375933cd","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f88bedbeb09c6f5a6645cb24c7c55f1aa22d19ae96c8e6959cbd8b85a707bc6","impliedFormat":1},{"version":"7fe93b39b810eadd916be8db880dd7f0f7012a5cc6ffb62de8f62a2117fa6f1f","impliedFormat":1},{"version":"bb0074cc08b84a2374af33d8bf044b80851ccc9e719a5e202eacf40db2c31600","impliedFormat":1},{"version":"1a7daebe4f45fb03d9ec53d60008fbf9ac45a697fdc89e4ce218bc94b94f94d6","impliedFormat":1},{"version":"f94b133a3cb14a288803be545ac2683e0d0ff6661bcd37e31aaaec54fc382aed","impliedFormat":1},{"version":"f59d0650799f8782fd74cf73c19223730c6d1b9198671b1c5b3a38e1188b5953","impliedFormat":1},{"version":"8a15b4607d9a499e2dbeed9ec0d3c0d7372c850b2d5f1fb259e8f6d41d468a84","impliedFormat":1},{"version":"26e0fe14baee4e127f4365d1ae0b276f400562e45e19e35fd2d4c296684715e6","impliedFormat":1},{"version":"1e9332c23e9a907175e0ffc6a49e236f97b48838cc8aec9ce7e4cec21e544b65","impliedFormat":1},{"version":"3753fbc1113dc511214802a2342280a8b284ab9094f6420e7aa171e868679f91","impliedFormat":1},{"version":"999ca32883495a866aa5737fe1babc764a469e4cde6ee6b136a4b9ae68853e4b","impliedFormat":1},{"version":"d6b1eba8496bdd0eed6fc8a685768fe01b2da4a0388b5fe7df558290bffcf32f","affectsGlobalScope":true,"impliedFormat":1},{"version":"7f57fc4404ff020bc45b9c620aff2b40f700b95fe31164024c453a5e3c163c54","impliedFormat":1},{"version":"7f57fc4404ff020bc45b9c620aff2b40f700b95fe31164024c453a5e3c163c54","impliedFormat":1},{"version":"2a2de5b9459b3fc44decd9ce6100b72f1b002ef523126c1d3d8b2a4a63d74d78","affectsGlobalScope":true,"impliedFormat":1},{"version":"f13f4b465c99041e912db5c44129a94588e1aafee35a50eab51044833f50b4ee","affectsGlobalScope":true,"impliedFormat":1},{"version":"eadcffda2aa84802c73938e589b9e58248d74c59cb7fcbca6474e3435ac15504","affectsGlobalScope":true,"impliedFormat":1},{"version":"105ba8ff7ba746404fe1a2e189d1d3d2e0eb29a08c18dded791af02f29fb4711","affectsGlobalScope":true,"impliedFormat":1},{"version":"00343ca5b2e3d48fa5df1db6e32ea2a59afab09590274a6cccb1dbae82e60c7c","affectsGlobalScope":true,"impliedFormat":1},{"version":"ebd9f816d4002697cb2864bea1f0b70a103124e18a8cd9645eeccc09bdf80ab4","affectsGlobalScope":true,"impliedFormat":1},{"version":"2c1afac30a01772cd2a9a298a7ce7706b5892e447bb46bdbeef720f7b5da77ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"7b0225f483e4fa685625ebe43dd584bb7973bbd84e66a6ba7bbe175ee1048b4f","affectsGlobalScope":true,"impliedFormat":1},{"version":"c0a4b8ac6ce74679c1da2b3795296f5896e31c38e888469a8e0f99dc3305de60","affectsGlobalScope":true,"impliedFormat":1},{"version":"3084a7b5f569088e0146533a00830e206565de65cae2239509168b11434cd84f","affectsGlobalScope":true,"impliedFormat":1},{"version":"c5079c53f0f141a0698faa903e76cb41cd664e3efb01cc17a5c46ec2eb0bef42","affectsGlobalScope":true,"impliedFormat":1},{"version":"32cafbc484dea6b0ab62cf8473182bbcb23020d70845b406f80b7526f38ae862","affectsGlobalScope":true,"impliedFormat":1},{"version":"fca4cdcb6d6c5ef18a869003d02c9f0fd95df8cfaf6eb431cd3376bc034cad36","affectsGlobalScope":true,"impliedFormat":1},{"version":"b93ec88115de9a9dc1b602291b85baf825c85666bf25985cc5f698073892b467","affectsGlobalScope":true,"impliedFormat":1},{"version":"f5c06dcc3fe849fcb297c247865a161f995cc29de7aa823afdd75aaaddc1419b","affectsGlobalScope":true,"impliedFormat":1},{"version":"b77e16112127a4b169ef0b8c3a4d730edf459c5f25fe52d5e436a6919206c4d7","affectsGlobalScope":true,"impliedFormat":1},{"version":"fbffd9337146eff822c7c00acbb78b01ea7ea23987f6c961eba689349e744f8c","affectsGlobalScope":true,"impliedFormat":1},{"version":"a995c0e49b721312f74fdfb89e4ba29bd9824c770bbb4021d74d2bf560e4c6bd","affectsGlobalScope":true,"impliedFormat":1},{"version":"c7b3542146734342e440a84b213384bfa188835537ddbda50d30766f0593aff9","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce6180fa19b1cccd07ee7f7dbb9a367ac19c0ed160573e4686425060b6df7f57","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f02e2476bccb9dbe21280d6090f0df17d2f66b74711489415a8aa4df73c9675","affectsGlobalScope":true,"impliedFormat":1},{"version":"45e3ab34c1c013c8ab2dc1ba4c80c780744b13b5676800ae2e3be27ae862c40c","affectsGlobalScope":true,"impliedFormat":1},{"version":"805c86f6cca8d7702a62a844856dbaa2a3fd2abef0536e65d48732441dde5b5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"e42e397f1a5a77994f0185fd1466520691456c772d06bf843e5084ceb879a0ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"f4c2b41f90c95b1c532ecc874bd3c111865793b23aebcc1c3cbbabcd5d76ffb0","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab26191cfad5b66afa11b8bf935ef1cd88fabfcb28d30b2dfa6fad877d050332","affectsGlobalScope":true,"impliedFormat":1},{"version":"2088bc26531e38fb05eedac2951480db5309f6be3fa4a08d2221abb0f5b4200d","affectsGlobalScope":true,"impliedFormat":1},{"version":"cb9d366c425fea79716a8fb3af0d78e6b22ebbab3bd64d25063b42dc9f531c1e","affectsGlobalScope":true,"impliedFormat":1},{"version":"500934a8089c26d57ebdb688fc9757389bb6207a3c8f0674d68efa900d2abb34","affectsGlobalScope":true,"impliedFormat":1},{"version":"689da16f46e647cef0d64b0def88910e818a5877ca5379ede156ca3afb780ac3","affectsGlobalScope":true,"impliedFormat":1},{"version":"bc21cc8b6fee4f4c2440d08035b7ea3c06b3511314c8bab6bef7a92de58a2593","affectsGlobalScope":true,"impliedFormat":1},{"version":"7ca53d13d2957003abb47922a71866ba7cb2068f8d154877c596d63c359fed25","affectsGlobalScope":true,"impliedFormat":1},{"version":"54725f8c4df3d900cb4dac84b64689ce29548da0b4e9b7c2de61d41c79293611","affectsGlobalScope":true,"impliedFormat":1},{"version":"e5594bc3076ac29e6c1ebda77939bc4c8833de72f654b6e376862c0473199323","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f3eb332c2d73e729f3364fcc0c2b375e72a121e8157d25a82d67a138c83a95c","affectsGlobalScope":true,"impliedFormat":1},{"version":"6f4427f9642ce8d500970e4e69d1397f64072ab73b97e476b4002a646ac743b1","affectsGlobalScope":true,"impliedFormat":1},{"version":"48915f327cd1dea4d7bd358d9dc7732f58f9e1626a29cc0c05c8c692419d9bb7","affectsGlobalScope":true,"impliedFormat":1},{"version":"b7bf9377723203b5a6a4b920164df22d56a43f593269ba6ae1fdc97774b68855","affectsGlobalScope":true,"impliedFormat":1},{"version":"db9709688f82c9e5f65a119c64d835f906efe5f559d08b11642d56eb85b79357","affectsGlobalScope":true,"impliedFormat":1},{"version":"4b25b8c874acd1a4cf8444c3617e037d444d19080ac9f634b405583fd10ce1f7","affectsGlobalScope":true,"impliedFormat":1},{"version":"37be57d7c90cf1f8112ee2636a068d8fd181289f82b744160ec56a7dc158a9f5","affectsGlobalScope":true,"impliedFormat":1},{"version":"a917a49ac94cd26b754ab84e113369a75d1a47a710661d7cd25e961cc797065f","affectsGlobalScope":true,"impliedFormat":1},{"version":"6d3261badeb7843d157ef3e6f5d1427d0eeb0af0cf9df84a62cfd29fd47ac86e","affectsGlobalScope":true,"impliedFormat":1},{"version":"195daca651dde22f2167ac0d0a05e215308119a3100f5e6268e8317d05a92526","affectsGlobalScope":true,"impliedFormat":1},{"version":"8b11e4285cd2bb164a4dc09248bdec69e9842517db4ca47c1ba913011e44ff2f","affectsGlobalScope":true,"impliedFormat":1},{"version":"0508571a52475e245b02bc50fa1394065a0a3d05277fbf5120c3784b85651799","affectsGlobalScope":true,"impliedFormat":1},{"version":"8f9af488f510c3015af3cc8c267a9e9d96c4dd38a1fdff0e11dc5a544711415b","affectsGlobalScope":true,"impliedFormat":1},{"version":"fc611fea8d30ea72c6bbfb599c9b4d393ce22e2f5bfef2172534781e7d138104","affectsGlobalScope":true,"impliedFormat":1},{"version":"0bd714129fca875f7d4c477a1a392200b0bcd13fb2e80928cd334b63830ea047","affectsGlobalScope":true,"impliedFormat":1},{"version":"e2c9037ae6cd2c52d80ceef0b3c5ffdb488627d71529cf4f63776daf11161c9a","affectsGlobalScope":true,"impliedFormat":1},{"version":"135d5cf4d345f59f1a9caadfafcd858d3d9cc68290db616cc85797224448cccc","affectsGlobalScope":true,"impliedFormat":1},{"version":"bc238c3f81c2984751932b6aab223cd5b830e0ac6cad76389e5e9d2ffc03287d","affectsGlobalScope":true,"impliedFormat":1},{"version":"4a07f9b76d361f572620927e5735b77d6d2101c23cdd94383eb5b706e7b36357","affectsGlobalScope":true,"impliedFormat":1},{"version":"7c4e8dc6ab834cc6baa0227e030606d29e3e8449a9f67cdf5605ea5493c4db29","affectsGlobalScope":true,"impliedFormat":1},{"version":"de7ba0fd02e06cd9a5bd4ab441ed0e122735786e67dde1e849cced1cd8b46b78","affectsGlobalScope":true,"impliedFormat":1},{"version":"6148e4e88d720a06855071c3db02069434142a8332cf9c182cda551adedf3156","affectsGlobalScope":true,"impliedFormat":1},{"version":"d63dba625b108316a40c95a4425f8d4294e0deeccfd6c7e59d819efa19e23409","affectsGlobalScope":true,"impliedFormat":1},{"version":"0568d6befee03dd435bed4fc25c4e46865b24bdcb8c563fdc21f580a2c301904","affectsGlobalScope":true,"impliedFormat":1},{"version":"30d62269b05b584741f19a5369852d5d34895aa2ac4fd948956f886d15f9cc0d","affectsGlobalScope":true,"impliedFormat":1},{"version":"f128dae7c44d8f35ee42e0a437000a57c9f06cc04f8b4fb42eebf44954d53dc8","affectsGlobalScope":true,"impliedFormat":1},{"version":"ffbe6d7b295306b2ba88030f65b74c107d8d99bdcf596ea99c62a02f606108b0","affectsGlobalScope":true,"impliedFormat":1},{"version":"996fb27b15277369c68a4ba46ed138b4e9e839a02fb4ec756f7997629242fd9f","affectsGlobalScope":true,"impliedFormat":1},{"version":"79b712591b270d4778c89706ca2cfc56ddb8c3f895840e477388f1710dc5eda9","affectsGlobalScope":true,"impliedFormat":1},{"version":"20884846cef428b992b9bd032e70a4ef88e349263f63aeddf04dda837a7dba26","affectsGlobalScope":true,"impliedFormat":1},{"version":"1ce14b81c5cc821994aa8ec1d42b220dd41b27fcc06373bce3958af7421b77d4","affectsGlobalScope":true,"impliedFormat":1},{"version":"b3a048b3e9302ef9a34ef4ebb9aecfb28b66abb3bce577206a79fee559c230da","affectsGlobalScope":true,"impliedFormat":1},{"version":"e03da518b01b46a4c99a1f88cd727ee98ddf14492c43dae1ae7a63e992971bab","impliedFormat":1},{"version":"70521b6ab0dcba37539e5303104f29b721bfb2940b2776da4cc818c07e1fefc1","affectsGlobalScope":true,"impliedFormat":1},{"version":"030e350db2525514580ed054f712ffb22d273e6bc7eddc1bb7eda1e0ba5d395e","affectsGlobalScope":true,"impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"a79e62f1e20467e11a904399b8b18b18c0c6eea6b50c1168bf215356d5bebfaf","affectsGlobalScope":true,"impliedFormat":1},{"version":"8fa51737611c21ba3a5ac02c4e1535741d58bec67c9bdf94b1837a31c97a2263","affectsGlobalScope":true,"impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"24bd580b5743dc56402c440dc7f9a4f5d592ad7a419f25414d37a7bfe11e342b","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"6bdc71028db658243775263e93a7db2fd2abfce3ca569c3cca5aee6ed5eb186d","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"d2bc987ae352271d0d615a420dcf98cc886aa16b87fb2b569358c1fe0ca0773d","affectsGlobalScope":true,"impliedFormat":1},{"version":"4f0539c58717cbc8b73acb29f9e992ab5ff20adba5f9b57130691c7f9b186a4d","impliedFormat":1},{"version":"7394959e5a741b185456e1ef5d64599c36c60a323207450991e7a42e08911419","impliedFormat":1},{"version":"76103716ba397bbb61f9fa9c9090dca59f39f9047cb1352b2179c5d8e7f4e8d0","impliedFormat":1},{"version":"f9677e434b7a3b14f0a9367f9dfa1227dfe3ee661792d0085523c3191ae6a1a4","affectsGlobalScope":true,"impliedFormat":1},{"version":"4314c7a11517e221f7296b46547dbc4df047115b182f544d072bdccffa57fc72","impliedFormat":1},{"version":"115971d64632ea4742b5b115fb64ed04bcaae2c3c342f13d9ba7e3f9ee39c4e7","impliedFormat":1},{"version":"c2510f124c0293ab80b1777c44d80f812b75612f297b9857406468c0f4dafe29","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"9057f224b79846e3a95baf6dad2c8103278de2b0c5eebda23fc8188171ad2398","affectsGlobalScope":true,"impliedFormat":1},{"version":"19d5f8d3930e9f99aa2c36258bf95abbe5adf7e889e6181872d1cdba7c9a7dd5","impliedFormat":1},{"version":"e6f5a38687bebe43a4cef426b69d34373ef68be9a6b1538ec0a371e69f309354","impliedFormat":1},{"version":"a6bf63d17324010ca1fbf0389cab83f93389bb0b9a01dc8a346d092f65b3605f","impliedFormat":1},{"version":"e009777bef4b023a999b2e5b9a136ff2cde37dc3f77c744a02840f05b18be8ff","impliedFormat":1},{"version":"1e0d1f8b0adfa0b0330e028c7941b5a98c08b600efe7f14d2d2a00854fb2f393","impliedFormat":1},{"version":"ee1ee365d88c4c6c0c0a5a5701d66ebc27ccd0bcfcfaa482c6e2e7fe7b98edf7","affectsGlobalScope":true,"impliedFormat":1},{"version":"88bc59b32d0d5b4e5d9632ac38edea23454057e643684c3c0b94511296f2998c","affectsGlobalScope":true,"impliedFormat":1},{"version":"1ff5a53a58e756d2661b73ba60ffe274231a4432d21f7a2d0d9e4f6aa99f4283","impliedFormat":1},{"version":"1e289f30a48126935a5d408a91129a13a59c9b0f8c007a816f9f16ef821e144e","impliedFormat":1},{"version":"2ea254f944dfe131df1264d1fb96e4b1f7d110195b21f1f5dbb68fdd394e5518","impliedFormat":1},{"version":"5135bdd72cc05a8192bd2e92f0914d7fc43ee077d1293dc622a049b7035a0afb","impliedFormat":1},{"version":"4f80de3a11c0d2f1329a72e92c7416b2f7eab14f67e92cac63bb4e8d01c6edc8","impliedFormat":1},{"version":"6d386bc0d7f3afa1d401afc3e00ed6b09205a354a9795196caed937494a713e6","impliedFormat":1},{"version":"f579f267a2f4c2278cca2ec84613e95059368b503ce96586972d304e5e40125b","affectsGlobalScope":true,"impliedFormat":1},{"version":"23459c1915878a7c1e86e8bdb9c187cddd3aea105b8b1dfce512f093c969bc7e","impliedFormat":1},{"version":"b1b6ee0d012aeebe11d776a155d8979730440082797695fc8e2a5c326285678f","impliedFormat":1},{"version":"45875bcae57270aeb3ebc73a5e3fb4c7b9d91d6b045f107c1d8513c28ece71c0","impliedFormat":1},{"version":"1dc73f8854e5c4506131c4d95b3a6c24d0c80336d3758e95110f4c7b5cb16397","affectsGlobalScope":true,"impliedFormat":1},{"version":"5f6f1d54779d0b9ed152b0516b0958cd34889764c1190434bbf18e7a8bb884cd","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f16a7e4deafa527ed9995a772bb380eb7d3c2c0fd4ae178c5263ed18394db2c","impliedFormat":1},{"version":"c6b4e0a02545304935ecbf7de7a8e056a31bb50939b5b321c9d50a405b5a0bba","impliedFormat":1},{"version":"fab29e6d649aa074a6b91e3bdf2bff484934a46067f6ee97a30fcd9762ae2213","impliedFormat":1},{"version":"8145e07aad6da5f23f2fcd8c8e4c5c13fb26ee986a79d03b0829b8fce152d8b2","impliedFormat":1},{"version":"e1120271ebbc9952fdc7b2dd3e145560e52e06956345e6fdf91d70ca4886464f","impliedFormat":1},{"version":"814118df420c4e38fe5ae1b9a3bafb6e9c2aa40838e528cde908381867be6466","impliedFormat":1},{"version":"f7b1df115dbd1b8522cba4f404a9f4fdcd5169e2137129187ffeee9d287e4fd1","impliedFormat":1},{"version":"c878f74b6d10b267f6075c51ac1d8becd15b4aa6a58f79c0cfe3b24908357f60","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"93452d394fdd1dc551ec62f5042366f011a00d342d36d50793b3529bfc9bd633","impliedFormat":1},{"version":"fbf68fc8057932b1c30107ebc37420f8d8dc4bef1253c4c2f9e141886c0df5ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"2754d8221d77c7b382096651925eb476f1066b3348da4b73fe71ced7801edada","impliedFormat":1},{"version":"993985beef40c7d113f6dd8f0ba26eed63028b691fbfeb6a5b63f26408dd2c6d","affectsGlobalScope":true,"impliedFormat":1},{"version":"bef91efa0baea5d0e0f0f27b574a8bc100ce62a6d7e70220a0d58af6acab5e89","affectsGlobalScope":true,"impliedFormat":1},{"version":"282fd2a1268a25345b830497b4b7bf5037a5e04f6a9c44c840cb605e19fea841","impliedFormat":1},{"version":"5360a27d3ebca11b224d7d3e38e3e2c63f8290cb1fcf6c3610401898f8e68bc3","impliedFormat":1},{"version":"66ba1b2c3e3a3644a1011cd530fb444a96b1b2dfe2f5e837a002d41a1a799e60","impliedFormat":1},{"version":"7e514f5b852fdbc166b539fdd1f4e9114f29911592a5eb10a94bb3a13ccac3c4","impliedFormat":1},{"version":"7d6ff413e198d25639f9f01f16673e7df4e4bd2875a42455afd4ecc02ef156da","affectsGlobalScope":true,"impliedFormat":1},{"version":"cb094bb347d7df3380299eb69836c2c8758626ecf45917577707c03cf816b6f4","affectsGlobalScope":true,"impliedFormat":1},{"version":"f689c4237b70ae6be5f0e4180e8833f34ace40529d1acc0676ab8fb8f70457d7","impliedFormat":1},{"version":"b02784111b3fc9c38590cd4339ff8718f9329a6f4d3fd66e9744a1dcd1d7e191","impliedFormat":1},{"version":"ac5ed35e649cdd8143131964336ab9076937fa91802ec760b3ea63b59175c10a","impliedFormat":1},{"version":"52a8e7e8a1454b6d1b5ad428efae3870ffc56f2c02d923467f2940c454aa9aec","affectsGlobalScope":true,"impliedFormat":1},{"version":"78dc0513cc4f1642906b74dda42146bcbd9df7401717d6e89ea6d72d12ecb539","impliedFormat":1},{"version":"ad90122e1cb599b3bc06a11710eb5489101be678f2920f2322b0ac3e195af78d","impliedFormat":1},{"version":"3b89216a7e38a454985ad17bb2ff85792837dc812f2a89fa5f60ad0a2e216fa7","impliedFormat":99},{"version":"16fe60bb544cfedfd2b5bb2f7d0b3957be7978706d57d9f06edc9c0c8dbdba23","impliedFormat":99},{"version":"de4a612aa8f1704af486f701e21993c786ba7d8cfed55fffa6684026aea2346e","impliedFormat":99},{"version":"c73fdf42528325dd17940937ed787b15ae3445c6a2dae1a2b74bc4d87d337ca2","impliedFormat":99},{"version":"e8e17dfef3cfa9f0847ac93dd535a9896af7fb57c1a1b164484bb1b0ee4a25d8","impliedFormat":99},{"version":"905aa259ceb7f7b7d1d59f99de8868c65ef476f04888e779f85dd1d7e1235223","impliedFormat":99},{"version":"638a22f8e16b31c37297483fa38cfc760ed309b16d4c606a7b4532f4394c9c0c","impliedFormat":99},{"version":"9394183f4c37b8591156f32e41223c9e0dd211eefe7499155d4cdf15268eaea8","impliedFormat":99},{"version":"b1aaa10e07510ebf74e94f6a0ec8c0df41cdc87bf4ec9d3907031b2a1ee6f602","impliedFormat":99},{"version":"7f429346f311ed5c346764a92cd89474bfb7d4012ee557801d6177bd60a81fee","impliedFormat":99},{"version":"86da92c883312ac2f3b35d0fdbda6196bdd614c3d72c386917f9c636b2eacb6d","impliedFormat":99},{"version":"7d3e062a778b8f5ea4f0cac7e925e31f88e6739812ebc5f827474324a4048f14","impliedFormat":99},{"version":"4f9e435035dddeab56b449e09ce1782be49c853304f156a2affcabca0a815862","impliedFormat":99},{"version":"4a838129e2aa98dbdb49a5f1a3367b74e204d92fbb1a66ebffbcb4dd8a4112e2","impliedFormat":99},{"version":"ad86350594b91ab1008decf71af218a49c62d8b1cb955c30a3984046dcbc86d7","impliedFormat":99},{"version":"64cad96d8eea46fcf925ccf652d01b1f841a3df1900d84c31cd26f15a97c9fc4","impliedFormat":99},{"version":"4e003c868b0d8f8ad200b96cbc653e18e513fa23e1c19c4fe3cc25d4394efc47","impliedFormat":99},{"version":"8887e70871f697fa42ad7cdf32168db60ec2d6760c173c97973e35377fe5d83b","impliedFormat":99},{"version":"e0864480ea083087d705f9405bd6bf59b795e8474c3447f0d6413b2bce535a09","impliedFormat":99},{"version":"e67cbea16f1994af89efd700542dbf3828a46a52b29e4d67e801bd7869dc103c","impliedFormat":99},{"version":"f582b0fcbf1eea9b318ab92fb89ea9ab2ebb84f9b60af89328a91155e1afce72","impliedFormat":99},{"version":"402e5c534fb2b85fa771170595db3ac0dd532112c8fa44fc23f233bc6967488b","impliedFormat":1},{"version":"52dcc257df5119fb66d864625112ce5033ac51a4c2afe376a0b299d2f7f76e4a","impliedFormat":1},{"version":"e5bab5f871ef708d52d47b3e5d0aa72a08ee7a152f33931d9a60809711a2a9a3","impliedFormat":1},{"version":"e16dc2a81595736024a206c7d5c8a39bfe2e6039208ef29981d0d95434ba8fcf","impliedFormat":1},{"version":"cc4a4903fb698ca1d961d4c10dce658aa3a479faf40509d526f122b044eaf6a4","impliedFormat":1},{"version":"19ee8416e6473ed6c7adb868fa796b5653cf0fa2a337658e677eaa0d134388c3","impliedFormat":1},{"version":"1328ab4e442614b28cdb3d4b414cf68325c0da0dca07287a338d0654b7a00261","impliedFormat":1},{"version":"a039dc21f045919f3cbee2ec13812cc6cc3eebc99dae4be00973230f468d19a6","impliedFormat":1},{"version":"3fbe57af01460e49dcd29df55d6931e1672bc6f1be0fb073d11410bc16f9037d","impliedFormat":1},{"version":"f760be449e8562ec5c09bb5187e8e1eabf3c113c0c58cddda53ef8c69f3e2131","impliedFormat":1},{"version":"44325ed13294fce6ab825b82947bbeed2611db7dad9d9135260192f375e5a189","impliedFormat":1},{"version":"e392e8fb5b514eafc585601c1d781485aa6dd6a320e75daf1064a4c6918a1b45","impliedFormat":1},{"version":"46e4a36e8ddbdfb4e7330e11c81c970dc8b218611df9183d39c41c5f8c653b55","impliedFormat":1},{"version":"370bde134aa8c2abc926d0e99d3a4d5d5dba65c6ee65459137e4f02670cbf841","impliedFormat":1},{"version":"6332f565867cf4a740a70e30f31cefba37ef7cebcf74f22eab8d744fde6d193e","impliedFormat":1},{"version":"2977b7884aedc895a1d0c9c210c7cf3272c29d6959a08a6fa3ff71e0aff08175","impliedFormat":1},{"version":"17f2922d41ddd032830a91371c948cd9ce903b35c95adca72271a54584f19b0b","impliedFormat":1},{"version":"3eed76ede2a1a14d7c9bb0a642041282dcc264811139d3dd275c9fe14efc9840","impliedFormat":1},{"version":"354a7f8e1287d9d6b7561bc97fdd8cbc2f7c1dd79e4cb37b942e8a5cfaff1085","impliedFormat":1},{"version":"8d369483f0c2b9ee388129cfdb6a43bc8112b377e86a41884bd06e19ce04f4c1","impliedFormat":99},{"version":"3fd8a5aefd8c3feb3936ca66f5aa89dff7bf6e6537b4158dbd0f6e0d65ed3b9e","impliedFormat":1},{"version":"a18642ddf216f162052a16cba0944892c4c4c977d3306a87cb673d46abbb0cbf","impliedFormat":1},{"version":"41c41c6e90133bb2a14f7561f29944771886e5535945b2b372e2f6ed6987746e","impliedFormat":1},{"version":"4ec16d7a4e366c06a4573d299e15fe6207fc080f41beac5da06f4af33ea9761e","impliedFormat":99},{"version":"960bd764c62ac43edc24eaa2af958a4b4f1fa5d27df5237e176d0143b36a39c6","affectsGlobalScope":true,"impliedFormat":99},{"version":"d2bb2bc576d9ddfc7c8aa6dbec440b6d8464abeaf46e3a0e4ed65b0c339f7ff6","impliedFormat":99},{"version":"59f8dc89b9e724a6a667f52cdf4b90b6816ae6c9842ce176d38fcc973669009e","affectsGlobalScope":true,"impliedFormat":99},{"version":"a6fad0438acd1d5b8eff4c0fcbe5a3b7810e688f641149f2ea6714e1b8b5e74b","impliedFormat":99},{"version":"2faebfa830ae4cfbfb58e48b0ec20a2a63882d776f0ca36ec7155d45cf1b7f2d","impliedFormat":99},{"version":"9b98d6982d3a305952855f00b660b60d0d625f0965281f1692d5df8a401fca00","impliedFormat":99},{"version":"c1a2e05eb6d7ca8d7e4a7f4c93ccf0c2857e842a64c98eaee4d85841ee9855e6","impliedFormat":1},{"version":"835fb2909ce458740fb4a49fc61709896c6864f5ce3db7f0a88f06c720d74d02","impliedFormat":1},{"version":"6e5857f38aa297a859cab4ec891408659218a5a2610cd317b6dcbef9979459cc","impliedFormat":1},{"version":"ead8e39c2e11891f286b06ae2aa71f208b1802661fcdb2425cffa4f494a68854","impliedFormat":1},{"version":"40ba6c32eb732a09e4446ade5cb6ad0c147f186f9c9dc6878b90b4418ad9f6ea","impliedFormat":1},{"version":"fdd814741843f85c98281522c58f5a646590ba9019fad2efaa95987655e0611b","impliedFormat":1},{"version":"c78aff4fb58b28b8f642d5095fc7eeb79f00e652a67caa19693af1adabb833c9","impliedFormat":1},{"version":"f80a08ced8818dc99359c0acd5b3f12762e1ce53758007759b0d4e503cbf4a5e","impliedFormat":1},{"version":"37935fa7564bcc6e0bc845b766a24391098d26f7c8245d6e8ab37bc016816e94","impliedFormat":1},{"version":"68add36d9632bc096d7245d24d6b0b8ad5f125183016102a3dad4c9c2438ccb0","impliedFormat":1},{"version":"3a819c2928ee06bbcc84e2797fd3558ae2ebb7e0ed8d87f71732fb2e2acc87b4","impliedFormat":1},{"version":"0f8a263f4c8595c8a07de52e3f3927640c44386c1aa2984de9eae50d75e613b2","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"346fffde7c32da87c2196eb7494422449dc2ca82d3b4e6bf55be1d1a33ffc2b0","impliedFormat":1},{"version":"add0ce7b77ba5b308492fa68f77f24d1ed1d9148534bdf05ac17c30763fc1a79","impliedFormat":1},{"version":"8b5875e4958528042103fdd775e106a7f76bafc29709f0690df9a7d2241d52a7","impliedFormat":1},{"version":"2f67911e4bf4e0717dc2ded248ce2d5e4398d945ee13889a6852c1233ea41508","impliedFormat":1},{"version":"d8430c275b0f59417ea8e173cfb888a4477b430ec35b595bf734f3ec7a7d729f","impliedFormat":1},{"version":"69364df1c776372d7df1fb46a6cb3a6bf7f55e700f533a104e3f9d70a32bec18","impliedFormat":1},{"version":"6042774c61ece4ba77b3bf375f15942eb054675b7957882a00c22c0e4fe5865c","impliedFormat":1},{"version":"5a3bd57ed7a9d9afef74c75f77fce79ba3c786401af9810cdf45907c4e93f30e","impliedFormat":1},{"version":"aef26cf95593c8ace1c62c4724f9afac77bdfa756fb8a00613cd152117cb2f43","impliedFormat":1},{"version":"30db853bb2e60170ba11e39ab48bacecb32d06d4def89eedf17e58ebab762a65","impliedFormat":1},{"version":"e27451b24234dfed45f6cf22112a04955183a99c42a2691fb4936d63cfe42761","impliedFormat":1},{"version":"2316301dd223d31962d917999acf8e543e0119c5d24ec984c9f22cb23247160c","impliedFormat":1},{"version":"58d65a2803c3b6629b0e18c8bf1bc883a686fcf0333230dd0151ab6e85b74307","impliedFormat":1},{"version":"e818471014c77c103330aee11f00a7a00b37b35500b53ea6f337aefacd6174c9","impliedFormat":1},{"version":"268fd6d9f2e807a39a6c5aa654b00f949feb63d3faa7dd0f9bba7dde9172159c","impliedFormat":1},{"version":"29f823cbe0166e10e7176a94afe609a24b9e5af3858628c541ff8ce1727023cd","impliedFormat":1},{"version":"7f2538d8c87826d50efd3e93e42af39512ef51d2daaa123a2c5777d5bd487249","impliedFormat":99},{"version":"8d5e0e73398be4d0a83385a3d82a2c9480e1e3f89fe9049e04d9d2df7148b584","impliedFormat":99},{"version":"c3a309a7fca8abfa9bb83339f3cbbf6ddc8e8eae5042efb1c0a29c0beeb715cc","impliedFormat":99},{"version":"d275ea05e287219ab244ffee7fdc2ad5abe0bc0659ef99cc5f0b301583a14e39","impliedFormat":99},{"version":"bdfa3b05b56953affb355a81976e6bfb35961ce63b746d6382ce3412c8e0be10","impliedFormat":99},{"version":"3b843a12e103c87d3c41909bc007bcee9e2f2ec823fbe071be3021155b9ad89f","impliedFormat":99},{"version":"2c5ad98ceeb9cb0e18ec5909ef47402b8094d0c3bed0cc1b9e5eb3880c2c0b71","impliedFormat":99},{"version":"c42a5f7247ac08f40c2b295505f5930ed1136d056ec708bf1bc4e2da5ee6c93c","impliedFormat":99},{"version":"2aca06dbaa1447c93b7a8056fa35f155f2ccc79b70519bda5cf63c61a7e7f3be","impliedFormat":99},{"version":"1993bb31a003e8b913ee4dbbc861bf946de37317e69a3efa662f8c8f34cd49ac","impliedFormat":99},{"version":"34733ad4c9e2cb3c1e4ac191a90896c414da50e8a6c63bf58e4856dfb71fec32","impliedFormat":99},{"version":"3186003e4b6a02b58f69d58777c168b06e7e549daa2eaeebc046ebab23896e12","impliedFormat":99},{"version":"f1dc33017496a5e54986ce937dffc2a3f47a296a98b7f9d88237f23b937ce88c","impliedFormat":99},{"version":"081b7ae9b48025c0f4a41e3a836ef0386b2a9331da468a77d001b8cf06aa2953","impliedFormat":99},{"version":"50af87ce54e6431e128d099e5bdda9473d0c86784d225c87f6c63d77e4810e7e","impliedFormat":99},{"version":"3c66360c23a470ac334c6e0dfbfe590caa2eaa2436a593accfe91c8544a11163","impliedFormat":99},{"version":"594dc14e127f99209325d06010f3fb15ef1e84d22bb6c7ae54da0e188d72f4bc","impliedFormat":99},{"version":"f2bcdf17cb7d1feaf874f01f14b19928f3fab065b0c9a1c5a63b59b37dc642f3","impliedFormat":99},{"version":"6f78a0137da4212b0cfc298179f5b49dbbd274508a4507610050b74cc1e633af","impliedFormat":99},{"version":"d1484c07a7d7427a638831ddc12dc12d6c1f6d80bf7d02fc3d189d299133f7bc","impliedFormat":99},{"version":"8857c6a8eb4958e8d819ea699f3bcdcd4005158f68bf0e54cdc7de9c2085caf1","impliedFormat":99},{"version":"577cbeac45591d2eafa0cfdf4f6c541caca6e78d3afbdbefa1461063cf3f007b","impliedFormat":99},{"version":"613d9ff1d4d948d04979c885c1eb03c79c946712d278bf649f5e6bc2d77de982","impliedFormat":99},{"version":"b21a03a19f7366033468599abddbb7cd1dfb5c6545063e133319323e252dabcb","impliedFormat":99},{"version":"6bb5c599d30dfb2754a8275a5ed25e1f08c955e04a0c35beec7042f2497cacf6","impliedFormat":99},{"version":"3fcbd17c5881f0fc01a15e96d85a73f9fedac5660aed9ac6a42dbbed0bf835eb","impliedFormat":99},{"version":"22ad761a3905a4d656c06afe9db1ef680ca3d4960f5cb0d5b37b99b452e7d7fe","impliedFormat":99},{"version":"e32e4e463345b7260be4e8491193733663c4a4ecb9a2b59d4a9ed744df503170","impliedFormat":99},{"version":"319e2acec4c6860ccea1afdd039b48e7d1ab4e4b63f75843e064cd0f66e8b9f4","impliedFormat":99},{"version":"8e33e9fe09500b4621d8f9d593d63d5379cca7edb6ff47a61df3abbef0c3d7b2","impliedFormat":99},{"version":"63a4d5c4a59b0aa7a47e203ff69c01b1bf83570b58e7a8f48d11183ee4cfc9b5","impliedFormat":99},{"version":"3b4f5b5cc75134b6cf5c6479fde4c2b77921ec242905141fbdbe31b069c4e0e6","impliedFormat":99},{"version":"758a76f71fff08d60e921ac2e66835b663dd9ff1c7a00e89144f2de926e44974","impliedFormat":99},{"version":"28c6caf88a757b504f8a36901e4cb7571098f92eaf971aaf8ff77101e47a72ac","impliedFormat":99},{"version":"6cf47dd3dbd9045354db60f6c531fe4836543d7c7b1bb32efa89b6df3631f22e","impliedFormat":99},{"version":"1399437c7d2b7d6d38d59ab08da5ef46c7c2b8388547254c1f70ba6deb336eb2","impliedFormat":99},{"version":"267f786bede38af3284a7991ab18e6ff857234e261028c7412e642c5faab9a73","impliedFormat":99},{"version":"94e84eea831f0d5675b20133d93b0959e1e9e7496ca51c37395d15ae32b06ca4","impliedFormat":99},{"version":"537f860f61604a1cc8597fa193d6eaf8ac994e67b821e74d1d5cdca1bf748f55","impliedFormat":99},{"version":"3ce983ec775cb7b11e530040c12ad8361b20d8156ad46e4c69bd0375b52316bc","impliedFormat":99},{"version":"d86e7468f7747ff55f9a6f304b986301238284fae0782aee67d248d76244ce2a","impliedFormat":99},{"version":"b485dcda88ed8040568701e04d46090549af479c9808ff2eb3be92db19a844ec","affectsGlobalScope":true,"impliedFormat":99},{"version":"cba6c66e04d07fea6613184f37f69c8596eb4502d4bc5ffc055340ac6371bc74","affectsGlobalScope":true,"impliedFormat":99},{"version":"8520c7e91f6e5db6e682c3c7b643fb3f944305fbbf2666fa8fd899eed74ce883","impliedFormat":99},{"version":"29722c56ed98362b7d50f210902e65a17545c3ac12ac279930c544f1d2e6e54f","impliedFormat":99},{"version":"c86bdb6d5c7812265eaff38d99a2ada25282bec29030ee484275f156f8bed4fc","impliedFormat":99},{"version":"2e32c03bf4ec527dee52b02f8c48c61c0a07e486e263a139e10ae39167a8dd96","impliedFormat":99},{"version":"5eddd8fc174138b38333ada55e5663085d0535011c4c67cc15fa2709e55062fa","impliedFormat":99},{"version":"907211f8273f2a7c010fa8a0c894bd1b30fbedc16234ca52b6391eaa8f76ec36","impliedFormat":99},{"version":"aed243366f9c480cd45623a8f2266c2c8868773cdb13db8f46acb2173cd407f1","impliedFormat":99},{"version":"9c372493f8a4e1c6276f1ba3e6521122b65d9de25d34dc8e43effc97fd64be54","impliedFormat":99},{"version":"d90b155a976d4312c36a3dd442bcd52e9326e599c523faae4cfb8942468f5298","impliedFormat":99},{"version":"13876cda8b941c4bff97ae9ddf89f2a04c8e4ff59bf9141f72f892815bf30244","impliedFormat":99},{"version":"f4cc088c0853a0bc7b5c65fcb53422898cb4a412ba46ed6c62716d465a351922","impliedFormat":99},{"version":"65657868876a64f14656e233a5f2e73d354fb824794f934e9543aa7c3aebf51d","impliedFormat":99},{"version":"b220e3999b3cbae6d40e997f4227bc4006ab04f511e5d4636dfd63badeffd025","impliedFormat":99},{"version":"79f8fe3684f8ddbc0d6e9246318aefd577e0f5af5bc3aafecccea72e4317d1a8","impliedFormat":99},{"version":"15b72d96f672fd6cc72e83bdeb1a2dbbece7bc3acc64b46662bf73f1cad71f7e","impliedFormat":99},{"version":"65302a5f3408266b1aa2fa9c30d9ed9407898e60194ebfe63fcbbf1914151523","impliedFormat":99},{"version":"407428ec4e5415b4958eac94ec0f0548541abf805f047f04160e32eb66d79283","impliedFormat":99},{"version":"2684c91788f5bca55e06039c8d2657adbaffd674f3ec47cb9cf391ca93c69a5a","impliedFormat":99},{"version":"ba22342d21065fed4c14eae3610b55af26b472d6d18758ae1138b435dce34105","impliedFormat":99},{"version":"744bb09f9baed7596d1fcca12760421e38269e1a0f9dd8a0da7882f7c8959742","impliedFormat":99},{"version":"fea649316c6e3015e731e55c186f2df57882d57dfece158b21c6d85bce28c6d0","impliedFormat":99},{"version":"6bf28e3d93b4913a0789e30270e6055fd21839e10d22faf7679dc728041e9c9e","impliedFormat":99},{"version":"cb119fef3876775bde929b54ce607825055686144b6a6ede6a0d31331eecd576","impliedFormat":99},{"version":"93a76ffaf5f0dd3b3673612f17abbcf8261965aa613d77d13ec9536d290c7161","impliedFormat":99},{"version":"e951c902bb4800ecaa1789b0e2952a9f2373ad1b3b2599e3e3f5182db79b33e4","impliedFormat":99},{"version":"d9f6a8b8878238bb600005b7d2f1bd8f8990990192decbdea2c6f10119fbd2cc","impliedFormat":99},{"version":"2575abaf61a3a4188b65156b381e5ff29ef0646e40f46885c3a645b642a1ef12","impliedFormat":99},{"version":"7eb36109cbb596b0f041c15bdd25cf57e7dc3d582623749ba049adbc1cb737f6","impliedFormat":99},{"version":"bef4c3fef2bbf42e70640dc596cd08cbd6f5d3134876c1eb9a0542b1735a2200","impliedFormat":99},{"version":"2055a99e69bb38d42d74bef757d18cb80dc20fd816a66301fcfe16d8594a51e3","impliedFormat":99},{"version":"2809e78cfc9cd793079bd90f4cb3af1cbb5cf7012d0bfd0fb1ba182b468e637f","impliedFormat":99},{"version":"79fc0c14761bc52dff40e21a6a202e37a6edd85e70aad8b00a549d85977c0965","impliedFormat":99},{"version":"b03b1b1f6170d34b921adfd21705eb73aa8b535b13bc1400316a72464045c676","impliedFormat":99},{"version":"1f41477880c70045dfc3276968d24f136db96fa89835569d0ea9bb3b36a8d595","impliedFormat":99},{"version":"21e5b2b5f72895023115a94412bba73691387aa3e48b1a6a29a5557bbe100b26","impliedFormat":99},{"version":"994499bf4af47bd3431a9a8ca9379a18704e7430dfca4643b9d480d80193d7af","impliedFormat":99},{"version":"1fcf5c1f3868f181609cbf9312fea5bf23074f3a9eea989772fedc03209d1905","impliedFormat":99},"19e37a1f20019dbf5e30f9fc52123c08e236a19ece0b35e661b18777f7b16bc5"],"root":[392],"options":{"allowSyntheticDefaultImports":true,"composite":true,"module":99,"skipLibCheck":true},"referencedMap":[[392,1],[185,2],[187,2],[188,3],[312,2],[359,4],[355,5],[313,6],[354,5],[372,7],[356,2],[315,8],[347,9],[346,10],[348,2],[351,2],[319,11],[349,12],[317,13],[357,12],[358,2],[318,14],[371,5],[320,2],[344,15],[316,16],[353,17],[321,18],[368,19],[360,20],[350,21],[342,22],[370,23],[343,24],[352,25],[361,26],[369,27],[367,28],[366,28],[365,14],[364,28],[362,28],[363,28],[314,13],[345,29],[382,30],[384,31],[386,2],[385,32],[380,33],[388,34],[381,35],[379,36],[387,31],[378,2],[383,31],[389,37],[373,2],[390,38],[391,39],[375,40],[376,40],[377,41],[374,2],[231,42],[230,2],[222,2],[224,43],[223,2],[218,44],[216,45],[217,46],[205,47],[206,45],[213,48],[204,49],[209,50],[219,2],[210,51],[215,52],[221,53],[220,54],[203,55],[211,56],[212,57],[207,58],[214,44],[208,59],[227,60],[190,61],[191,62],[194,63],[186,64],[193,65],[189,66],[184,2],[195,67],[196,68],[322,2],[324,69],[332,70],[327,71],[329,72],[328,71],[330,70],[333,73],[334,74],[335,2],[336,2],[331,73],[337,2],[338,75],[325,2],[339,2],[323,2],[340,70],[326,76],[341,77],[202,2],[80,2],[81,2],[15,2],[13,2],[14,2],[19,2],[18,2],[2,2],[20,2],[21,2],[22,2],[23,2],[24,2],[25,2],[26,2],[27,2],[3,2],[28,2],[29,2],[4,2],[30,2],[34,2],[31,2],[32,2],[33,2],[35,2],[36,2],[37,2],[5,2],[38,2],[39,2],[40,2],[41,2],[6,2],[45,2],[42,2],[43,2],[44,2],[46,2],[7,2],[47,2],[52,2],[53,2],[48,2],[49,2],[50,2],[51,2],[8,2],[57,2],[54,2],[55,2],[56,2],[58,2],[9,2],[59,2],[60,2],[61,2],[63,2],[62,2],[64,2],[65,2],[10,2],[66,2],[67,2],[68,2],[11,2],[69,2],[70,2],[71,2],[72,2],[73,2],[74,2],[12,2],[75,2],[82,2],[76,2],[77,2],[78,2],[79,2],[1,2],[17,2],[16,2],[182,78],[229,79],[198,80],[183,78],[181,2],[197,81],[228,2],[226,2],[199,2],[225,82],[192,83],[201,2],[200,84],[311,85],[305,86],[309,87],[306,87],[302,86],[310,88],[307,89],[308,87],[303,90],[304,91],[298,92],[239,93],[241,94],[297,2],[240,95],[301,96],[300,97],[299,98],[232,2],[242,93],[243,2],[234,99],[238,100],[233,2],[235,101],[236,102],[237,2],[244,103],[245,103],[246,103],[247,103],[248,103],[249,103],[250,103],[251,103],[252,103],[253,103],[254,103],[255,103],[256,103],[257,103],[259,103],[258,103],[260,103],[261,103],[262,103],[263,103],[264,103],[296,104],[265,103],[266,103],[267,103],[268,103],[269,103],[270,103],[271,103],[272,103],[273,103],[274,103],[275,103],[276,103],[277,103],[279,103],[278,103],[280,103],[281,103],[282,103],[283,103],[284,103],[285,103],[286,103],[287,103],[288,103],[289,103],[290,103],[291,103],[292,103],[295,103],[293,103],[294,103],[127,105],[128,105],[129,106],[88,107],[130,108],[131,109],[132,110],[83,2],[86,111],[84,2],[85,2],[133,112],[134,113],[135,114],[136,115],[137,116],[138,117],[139,117],[141,118],[140,119],[142,120],[143,121],[144,122],[126,123],[87,2],[145,124],[146,125],[147,126],[180,127],[148,128],[149,129],[150,130],[151,131],[152,132],[153,133],[154,134],[155,135],[156,136],[157,137],[158,137],[159,138],[160,2],[161,2],[162,139],[164,140],[163,141],[165,142],[166,143],[167,144],[168,145],[169,146],[170,147],[171,148],[172,149],[173,150],[174,151],[175,152],[176,153],[177,154],[178,155],[179,156],[104,157],[114,158],[103,157],[124,159],[95,160],[94,161],[123,162],[117,163],[122,164],[97,165],[111,166],[96,167],[120,168],[92,169],[91,162],[121,170],[93,171],[98,172],[99,2],[102,172],[89,2],[125,173],[115,174],[106,175],[107,176],[109,177],[105,178],[108,179],[118,162],[100,180],[101,181],[110,182],[90,183],[113,174],[112,172],[116,2],[119,184]],"affectedFilesPendingEmit":[[392,17]],"emitSignatures":[392],"version":"6.0.3"} \ No newline at end of file diff --git a/desktop/tsconfig.tsbuildinfo b/desktop/tsconfig.tsbuildinfo new file mode 100644 index 00000000000..ac7a3201f31 --- /dev/null +++ b/desktop/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/features-manifest.d.ts","./src/jdenticon.d.ts","./src/main.tsx","./src/upng-js.d.ts","./src/vite-env.d.ts","./src/app/app.tsx","./src/app/apphuddlebar.tsx","./src/app/apphuddleshell.tsx","./src/app/appprofilepanelprovider.tsx","./src/app/appshell.helpers.ts","./src/app/appshell.tsx","./src/app/appshellchannelsurface.tsx","./src/app/appshellcontext.tsx","./src/app/appshelloverlays.tsx","./src/app/apptopchrome.tsx","./src/app/apptopchromeportal.tsx","./src/app/appworkfloweditoroverlayprovider.tsx","./src/app/buzzthemesurfaces.tsx","./src/app/lazysettingsscreen.tsx","./src/app/relayconnectionoverlay.tsx","./src/app/rooterrorboundary.tsx","./src/app/terminalcontextoverridecontext.tsx","./src/app/themegrainientbackground.tsx","./src/app/communityviewtransition.ts","./src/app/huddlebackingchannelstorage.ts","./src/app/huddlechannelvisibility.ts","./src/app/routetree.gen.ts","./src/app/router.tsx","./src/app/routes.ts","./src/app/useappshelldesktopnotifications.ts","./src/app/useappshellkeyboardshortcuts.ts","./src/app/useappshelllifecycleeffects.ts","./src/app/useappshelltraymenu.tsx","./src/app/usechannelactivityprojection.ts","./src/app/usechannelbrowserdialog.ts","./src/app/useclosewindowshortcut.ts","./src/app/usecommunitynavigationtransitions.ts","./src/app/usehuddlepresentation.ts","./src/app/uselivehomefeedactions.ts","./src/app/usemarkasreadshortcuts.ts","./src/app/usereloadshortcut.ts","./src/app/usesettingsshortcuts.ts","./src/app/usetauriwindowdrag.ts","./src/app/useterminalcontext.ts","./src/app/usethreadactivityfeeditems.ts","./src/app/usetraymenu.ts","./src/app/usewebviewzoomshortcuts.ts","./src/app/navigation/backforwardchords.ts","./src/app/navigation/navigationguard.ts","./src/app/navigation/resolvesearchhitdestination.ts","./src/app/navigation/searchhighlightnavigation.ts","./src/app/navigation/searchhiteventcache.ts","./src/app/navigation/searchhitnavigation.ts","./src/app/navigation/useappnavigation.ts","./src/app/navigation/usebackforwardcontrols.ts","./src/app/routes/channelroutescreen.tsx","./src/app/routes/workflowsroutescreen.tsx","./src/app/routes/agents.tsx","./src/app/routes/channels.$channelid.posts.$postid.tsx","./src/app/routes/channels.$channelid.tsx","./src/app/routes/index.tsx","./src/app/routes/lazyworkflowsroutescreen.ts","./src/app/routes/messages.new.tsx","./src/app/routes/projects.$projectid.tsx","./src/app/routes/projects.tsx","./src/app/routes/pulse.tsx","./src/app/routes/reminders.tsx","./src/app/routes/root.tsx","./src/app/routes/searchhighlightroutestate.ts","./src/app/routes/settings.tsx","./src/app/routes/workflows.$workflowid.tsx","./src/app/routes/workflows.tsx","./src/features/agent-memory/hooks.ts","./src/features/agent-memory/lib/buildmemorygraph.ts","./src/features/agent-memory/ui/memorysection.tsx","./src/features/agents/acpruntimesquery.ts","./src/features/agents/activeagentturnsstore.ts","./src/features/agents/agentmanagement.ts","./src/features/agents/agentmanagementbuffer.ts","./src/features/agents/agentreuse.ts","./src/features/agents/agentworkingsignal.ts","./src/features/agents/cardmintstore.ts","./src/features/agents/channelagents.ts","./src/features/agents/hooks.ts","./src/features/agents/knownagentpubkeys.ts","./src/features/agents/managedagentreconciliationplan.ts","./src/features/agents/managedagentruntimehooks.ts","./src/features/agents/managedagentruntimestatus.ts","./src/features/agents/observerrelaystore.ts","./src/features/agents/opencreateagentevent.ts","./src/features/agents/openeditagentevent.ts","./src/features/agents/opensnapshotimportfromurlevent.ts","./src/features/agents/preventsleepactivity.ts","./src/features/agents/snapshothooks.ts","./src/features/agents/teamhooks.ts","./src/features/agents/useagentaccessowneronly.ts","./src/features/agents/useagentmanagement.ts","./src/features/agents/useagentobserveringestion.ts","./src/features/agents/usecreatedagentchannelattachment.ts","./src/features/agents/useglobalagentconfig.ts","./src/features/agents/useknownagentpubkeys.tsx","./src/features/agents/usemanagedagentruntimereconciliation.ts","./src/features/agents/useopenagentactivity.ts","./src/features/agents/usepreventsleep.ts","./src/features/agents/lib/agentaccesswarning.ts","./src/features/agents/lib/agentautocompleteeligibility.ts","./src/features/agents/lib/agentcardavatar.ts","./src/features/agents/lib/agentcardgallerystate.ts","./src/features/agents/lib/agentcardmodellabel.ts","./src/features/agents/lib/agentconfigcore.ts","./src/features/agents/lib/agentparallelism.ts","./src/features/agents/lib/autorestartpolicy.ts","./src/features/agents/lib/catalog.ts","./src/features/agents/lib/formatagentmodellabel.ts","./src/features/agents/lib/friendlyagentlasterror.ts","./src/features/agents/lib/instanceinputfordefinition.ts","./src/features/agents/lib/liveswitchoutcome.ts","./src/features/agents/lib/managedagentcontrolactions.ts","./src/features/agents/lib/othersetupagent.ts","./src/features/agents/lib/personacatalogrelay.ts","./src/features/agents/lib/personaeditcaches.ts","./src/features/agents/lib/personasavenotice.ts","./src/features/agents/lib/pickbotname.ts","./src/features/agents/lib/pickprofileagent.ts","./src/features/agents/lib/resolvepersonaruntime.ts","./src/features/agents/lib/respondtoallowlist.ts","./src/features/agents/lib/teamcatalogrelay.ts","./src/features/agents/lib/teampersonas.ts","./src/features/agents/lib/useagentsdatarefresh.ts","./src/features/agents/lib/useautorestartpolicy.ts","./src/features/agents/lib/usebotrecents.ts","./src/features/agents/lib/useinstalloutputline.ts","./src/features/agents/lib/uselastruntime.ts","./src/features/agents/lib/usepersonacatalogrelay.ts","./src/features/agents/lib/usepersonasync.ts","./src/features/agents/lib/useteamcatalogrelay.ts","./src/features/agents/ui/addagenttochanneldialog.tsx","./src/features/agents/ui/addcustomharnessdialog.tsx","./src/features/agents/ui/addteamtochanneldialog.tsx","./src/features/agents/ui/advancedrequiredbadge.tsx","./src/features/agents/ui/agentaiconfigurationmode.tsx","./src/features/agents/ui/agentaidefaults.tsx","./src/features/agents/ui/agentcardmintdialog.tsx","./src/features/agents/ui/agentcardviewerdialog.tsx","./src/features/agents/ui/agentconfigfields.tsx","./src/features/agents/ui/agentconfigpanel.tsx","./src/features/agents/ui/agentcreationpreview.tsx","./src/features/agents/ui/agentcreationpreview.utils.ts","./src/features/agents/ui/agentdefaultsdialog.tsx","./src/features/agents/ui/agentdefaultseditor.tsx","./src/features/agents/ui/agentdefinitiondialog.tsx","./src/features/agents/ui/agentdefinitiondialogfooter.tsx","./src/features/agents/ui/agentdefinitiondialogshell.tsx","./src/features/agents/ui/agentdefinitionmetadata.tsx","./src/features/agents/ui/agentdialog.tsx","./src/features/agents/ui/agentgrouprows.tsx","./src/features/agents/ui/agentharnessfield.tsx","./src/features/agents/ui/agentidentitycard.tsx","./src/features/agents/ui/agentinstanceeditdialog.tsx","./src/features/agents/ui/agentmanagementdialogs.tsx","./src/features/agents/ui/agentrunlocationcontext.tsx","./src/features/agents/ui/agentruntimeavatarcontrol.tsx","./src/features/agents/ui/agentsessiontoolitem.tsx","./src/features/agents/ui/agentsessiontranscriptlist.tsx","./src/features/agents/ui/agentsnapshotexportdialog.tsx","./src/features/agents/ui/agentsnapshotimportdialog.tsx","./src/features/agents/ui/agentstatusbadge.tsx","./src/features/agents/ui/agentsscreen.tsx","./src/features/agents/ui/agentsview.tsx","./src/features/agents/ui/cardmintcomposerchip.tsx","./src/features/agents/ui/cardmintkeycue.tsx","./src/features/agents/ui/communitycatalogdialog.tsx","./src/features/agents/ui/copybutton.tsx","./src/features/agents/ui/createidentitycard.tsx","./src/features/agents/ui/createnewbutton.tsx","./src/features/agents/ui/editagentadvancedfields.tsx","./src/features/agents/ui/effortpickerfield.tsx","./src/features/agents/ui/envvarseditor.tsx","./src/features/agents/ui/filecontentblock.tsx","./src/features/agents/ui/fileeditdiffview.tsx","./src/features/agents/ui/harnesscatalogretrynotice.tsx","./src/features/agents/ui/identityinitialsavatar.tsx","./src/features/agents/ui/managedagentlogpanel.tsx","./src/features/agents/ui/managedagentrow.tsx","./src/features/agents/ui/managedagentsessionpanel.tsx","./src/features/agents/ui/mcpserverssection.tsx","./src/features/agents/ui/modelpicker.tsx","./src/features/agents/ui/othersetupagentmarker.tsx","./src/features/agents/ui/owneronlyaccessfield.tsx","./src/features/agents/ui/personaactionsmenu.tsx","./src/features/agents/ui/personaaddedby.tsx","./src/features/agents/ui/personaadvancedfields.tsx","./src/features/agents/ui/personadeletedialog.tsx","./src/features/agents/ui/personadropdownfield.tsx","./src/features/agents/ui/personamodelcombobox.tsx","./src/features/agents/ui/personamodelfield.tsx","./src/features/agents/ui/personaproviderapikeyfield.tsx","./src/features/agents/ui/personasharedialog.tsx","./src/features/agents/ui/personasharerecipients.tsx","./src/features/agents/ui/promptsectionaccordion.tsx","./src/features/agents/ui/providerconfigfields.tsx","./src/features/agents/ui/raweventrail.tsx","./src/features/agents/ui/removemembersconfirmdialog.tsx","./src/features/agents/ui/requestedagentcreatedialogs.tsx","./src/features/agents/ui/respondtofield.tsx","./src/features/agents/ui/restartdiffbadge.tsx","./src/features/agents/ui/runonsummarysection.tsx","./src/features/agents/ui/snapshotoptionmenu.tsx","./src/features/agents/ui/teamdeletedialog.tsx","./src/features/agents/ui/teamdialog.tsx","./src/features/agents/ui/teamidentitycard.tsx","./src/features/agents/ui/teamsharedialog.tsx","./src/features/agents/ui/teamsnapshotexportdialog.tsx","./src/features/agents/ui/teamsnapshotimportdialog.tsx","./src/features/agents/ui/teamssection.tsx","./src/features/agents/ui/turnlivenessindicator.tsx","./src/features/agents/ui/unifiedagentssection.tsx","./src/features/agents/ui/wheretorunsection.tsx","./src/features/agents/ui/addcustomharness.ts","./src/features/agents/ui/agentaiconfigurationpolicy.ts","./src/features/agents/ui/agentconfigcontrols.tsx","./src/features/agents/ui/agentconfigoptions.tsx","./src/features/agents/ui/agentcreateintent.ts","./src/features/agents/ui/agentdefinitionsubmitpayload.ts","./src/features/agents/ui/agentprofilesyncwarning.ts","./src/features/agents/ui/agentsessionfileeditdiff.ts","./src/features/agents/ui/agentsessionfileread.ts","./src/features/agents/ui/agentsessionimagecontent.ts","./src/features/agents/ui/agentsessionpanellayout.ts","./src/features/agents/ui/agentsessiontoolcatalog.ts","./src/features/agents/ui/agentsessiontoolclassifier.ts","./src/features/agents/ui/agentsessiontoolsummary.ts","./src/features/agents/ui/agentsessiontranscript.ts","./src/features/agents/ui/agentsessiontranscriptcontext.ts","./src/features/agents/ui/agentsessiontranscriptgrouping.ts","./src/features/agents/ui/agentsessiontranscripthelpers.ts","./src/features/agents/ui/agentsessiontranscriptpresentation.ts","./src/features/agents/ui/agentsessiontypes.ts","./src/features/agents/ui/agentsessionutils.ts","./src/features/agents/ui/agentui.ts","./src/features/agents/ui/archivepagingstate.ts","./src/features/agents/ui/bakedenvhelpers.ts","./src/features/agents/ui/buzzagentconfig.ts","./src/features/agents/ui/buzzagentmodeltuningfields.tsx","./src/features/agents/ui/cardmintkeyutils.ts","./src/features/agents/ui/catalogownerlabel.ts","./src/features/agents/ui/effortpicker.ts","./src/features/agents/ui/globalagentcredentialstate.ts","./src/features/agents/ui/managedagentavatar.ts","./src/features/agents/ui/modelcapabilities.ts","./src/features/agents/ui/personabehaviordraft.ts","./src/features/agents/ui/personadialogenvvars.ts","./src/features/agents/ui/personadialogstate.ts","./src/features/agents/ui/personalibrarycopy.ts","./src/features/agents/ui/personamodeldiscoverystatus.ts","./src/features/agents/ui/personaruntimemodel.ts","./src/features/agents/ui/providerapikeyfieldstate.ts","./src/features/agents/ui/providerenvvarupdates.ts","./src/features/agents/ui/relaymeshmodelpicker.ts","./src/features/agents/ui/runonsummary.ts","./src/features/agents/ui/runtimeavailabilitywarning.ts","./src/features/agents/ui/runtimemodelproviderselection.ts","./src/features/agents/ui/snapshotavatarpng.ts","./src/features/agents/ui/teamdialogselection.ts","./src/features/agents/ui/teamlibrarycopy.ts","./src/features/agents/ui/teamsnapshotimport.lib.ts","./src/features/agents/ui/transcriptanimationpreference.ts","./src/features/agents/ui/transcripttimestamppreference.ts","./src/features/agents/ui/unifiedagentgroups.ts","./src/features/agents/ui/useagentdialogdefaults.ts","./src/features/agents/ui/usemanagedagentactions.ts","./src/features/agents/ui/useobserverevents.ts","./src/features/agents/ui/usepersonaactions.ts","./src/features/agents/ui/usepersonamodeldiscovery.ts","./src/features/agents/ui/userequiredcredentialstate.ts","./src/features/agents/ui/usesnapshotsendcontroller.ts","./src/features/agents/ui/useteamactions.ts","./src/features/agents/ui/usewindowfiledragover.ts","./src/features/agents/ui/wheretorunintent.ts","./src/features/agents/ui/agentsessiontoolitem/compactmessagesummary.tsx","./src/features/agents/ui/agentsessiontoolitem/compacttoolsummaryrow.tsx","./src/features/agents/ui/agentsessiontoolitem/sentmessagecontextdialog.tsx","./src/features/agents/ui/agentsessiontoolitem/shellcommandblock.tsx","./src/features/agents/ui/agentsessiontoolitem/todotoolsummary.tsx","./src/features/agents/ui/agentsessiontoolitem/tooldetailblocks.tsx","./src/features/agents/ui/agentsessiontoolitem/toolitem.tsx","./src/features/agents/ui/agentsessiontoolitem/viewimagetoolpreview.tsx","./src/features/agents/ui/agentsessiontoolitem/messagelinks.ts","./src/features/agents/ui/agentsessiontoolitem/usesentmessagebody.ts","./src/features/agents/ui/activityrenderclasses/activityrow.tsx","./src/features/agents/ui/activityrenderclasses/lifecycleactivity.tsx","./src/features/agents/ui/activityrenderclasses/messageactivity.tsx","./src/features/agents/ui/activityrenderclasses/messagelinkhovercue.tsx","./src/features/agents/ui/activityrenderclasses/planactivity.tsx","./src/features/agents/ui/activityrenderclasses/rawrailactivity.tsx","./src/features/agents/ui/activityrenderclasses/suppressedactivity.tsx","./src/features/agents/ui/activityrenderclasses/thoughtactivity.tsx","./src/features/agents/ui/activityrenderclasses/toolactivity.tsx","./src/features/agents/ui/activityrenderclasses/transcriptactivityitem.tsx","./src/features/agents/ui/activityrenderclasses/transcripttimestamp.tsx","./src/features/agents/ui/activityrenderclasses/usermessagebubble.tsx","./src/features/agents/ui/activityrenderclasses/types.ts","./src/features/agents/ui/activityrenderclasses/usetranscriptbubbleoverflow.ts","./src/features/channel-templates/hooks.ts","./src/features/channel-templates/useapplytemplate.ts","./src/features/channels/channelmemberprofilecache.ts","./src/features/channels/channelsnapshot.ts","./src/features/channels/dmresurface.ts","./src/features/channels/focusedthreadcloserequest.ts","./src/features/channels/forcedunreadstore.ts","./src/features/channels/hiddendmresurfaceaction.ts","./src/features/channels/hiddendmresurfacecoordinator.ts","./src/features/channels/hooks.ts","./src/features/channels/isdmnotifiablekind.ts","./src/features/channels/observedunreadstorage.ts","./src/features/channels/openchanneldirectory.ts","./src/features/channels/refreshchannelswhenidle.ts","./src/features/channels/rosterfreshness.ts","./src/features/channels/threadactivitystorage.ts","./src/features/channels/unreadchannelcounts.ts","./src/features/channels/unreadmembership.ts","./src/features/channels/unreadrootidstore.ts","./src/features/channels/useactivechannelheader.ts","./src/features/channels/usecanaddchannelmembers.ts","./src/features/channels/usechannelpanehandlers.ts","./src/features/channels/usedmresurfacefrommessages.ts","./src/features/channels/useephemeralchanneldisplay.ts","./src/features/channels/usehiddendmids.ts","./src/features/channels/uselivechannelupdates.ts","./src/features/channels/usemembershipnotifications.ts","./src/features/channels/usemessageeventprofilepubkeys.ts","./src/features/channels/usemessageownerprofiles.ts","./src/features/channels/useobservedunreadpersistence.ts","./src/features/channels/usethreadactivitypersistence.ts","./src/features/channels/usethreadtargetsync.ts","./src/features/channels/useunreadchannels.ts","./src/features/channels/usewelcomeagentcreate.ts","./src/features/channels/lib/canonicalchannelname.ts","./src/features/channels/lib/channeldescription.ts","./src/features/channels/lib/channellifecycle.ts","./src/features/channels/lib/channelmemberadmission.ts","./src/features/channels/lib/channelrecency.ts","./src/features/channels/lib/channelrecencymerge.ts","./src/features/channels/lib/channelsearchscore.ts","./src/features/channels/lib/dmhuddlemembers.ts","./src/features/channels/lib/dmparticipantdisplay.ts","./src/features/channels/lib/ephemeralchannel.ts","./src/features/channels/lib/huddleavailability.ts","./src/features/channels/lib/memberutils.ts","./src/features/channels/lib/subtreecreatedat.ts","./src/features/channels/lib/threadbadgecounts.ts","./src/features/channels/lib/threadfocuslayout.ts","./src/features/channels/lib/threadpanellayout.ts","./src/features/channels/lib/threadreplyunreadcounts.ts","./src/features/channels/lib/threadviewmodepreference.ts","./src/features/channels/lib/useclassifiedmembers.ts","./src/features/channels/readstate/readstateformat.ts","./src/features/channels/readstate/readstateidentity.ts","./src/features/channels/readstate/readstatemanager.ts","./src/features/channels/readstate/readstatesnapshot.ts","./src/features/channels/readstate/readstatestorage.ts","./src/features/channels/readstate/usereadstate.ts","./src/features/channels/ui/addchannelbotdialog.tsx","./src/features/channels/ui/addchannelbotgenericsection.tsx","./src/features/channels/ui/addchannelbotpersonassection.tsx","./src/features/channels/ui/addchannelbotreuseguard.tsx","./src/features/channels/ui/addchannelbotteamssection.tsx","./src/features/channels/ui/addmembersearchresultrow.tsx","./src/features/channels/ui/agentsessionthreadpanel.tsx","./src/features/channels/ui/botactivitybar.tsx","./src/features/channels/ui/channelbrowserdialog.tsx","./src/features/channels/ui/channelcanvas.tsx","./src/features/channels/ui/channelcomposeractivityaccessory.tsx","./src/features/channels/ui/channelglyph.tsx","./src/features/channels/ui/channelheaderstatusbadge.tsx","./src/features/channels/ui/channelmanagementauxiliarypanel.tsx","./src/features/channels/ui/channelmanagementmoderationactions.tsx","./src/features/channels/ui/channelmanagementsheet.tsx","./src/features/channels/ui/channelmanagementsheetrows.tsx","./src/features/channels/ui/channelmemberavatarstack.tsx","./src/features/channels/ui/channelmemberinvitecard.tsx","./src/features/channels/ui/channelmembersbar.tsx","./src/features/channels/ui/channelpane.helpers.ts","./src/features/channels/ui/channelpane.tsx","./src/features/channels/ui/channelpane.types.ts","./src/features/channels/ui/channelpermissionssettings.tsx","./src/features/channels/ui/channelscreen.tsx","./src/features/channels/ui/channelscreen.types.ts","./src/features/channels/ui/channelscreenemptystate.tsx","./src/features/channels/ui/channelscreenheader.tsx","./src/features/channels/ui/channelscreenlazyviews.ts","./src/features/channels/ui/channelscreenloadingfallback.tsx","./src/features/channels/ui/channeltypepicker.tsx","./src/features/channels/ui/channeltypesettings.tsx","./src/features/channels/ui/channelworkflowssection.tsx","./src/features/channels/ui/editrespondtodialog.tsx","./src/features/channels/ui/ephemeralchannelbadge.tsx","./src/features/channels/ui/focusthreaddrawer.tsx","./src/features/channels/ui/forumchannelcontent.tsx","./src/features/channels/ui/guardedchannelpane.tsx","./src/features/channels/ui/idleauxiliarypanel.tsx","./src/features/channels/ui/memberssidebar.tsx","./src/features/channels/ui/memberssidebaragentcontrols.tsx","./src/features/channels/ui/memberssidebarmembercard.tsx","./src/features/channels/ui/quickbotbar.tsx","./src/features/channels/ui/rightauxiliarypane.tsx","./src/features/channels/ui/threadpanelsurface.tsx","./src/features/channels/ui/threadviewmodetoggle.tsx","./src/features/channels/ui/welcomeagentcreatedialog.tsx","./src/features/channels/ui/welcomecomposerbanner.tsx","./src/features/channels/ui/agentsessionselection.ts","./src/features/channels/ui/channelformstyles.ts","./src/features/channels/ui/channelsearchkeys.ts","./src/features/channels/ui/searchtargetforwarding.tsx","./src/features/channels/ui/usechannelactivitytyping.ts","./src/features/channels/ui/usechannelagentsessions.ts","./src/features/channels/ui/usechannelintro.tsx","./src/features/channels/ui/usechannelopenreadstate.ts","./src/features/channels/ui/usechannelpanemessages.ts","./src/features/channels/ui/usechannelpanelhistorystate.ts","./src/features/channels/ui/usechannelprofilepanel.ts","./src/features/channels/ui/usechannelroutetarget.ts","./src/features/channels/ui/usechanneltargetreset.ts","./src/features/channels/ui/usechannelunreadstate.ts","./src/features/channels/ui/useeffectiveruntimes.ts","./src/features/channels/ui/usefocusdrawerpresence.ts","./src/features/channels/ui/usehuddlechannelmessages.ts","./src/features/channels/ui/usehuddlereadmarker.ts","./src/features/channels/ui/usehuddlethreadisolation.ts","./src/features/channels/ui/useinchannelpersonaids.ts","./src/features/channels/ui/usememberssidebaractions.ts","./src/features/channels/ui/usememberssidebarmoderation.ts","./src/features/channels/ui/usemessageprofiles.ts","./src/features/channels/ui/usenavigationguard.ts","./src/features/channels/ui/usepreparedmsendchannel.ts","./src/features/channels/ui/usequickbotdrop.ts","./src/features/channels/ui/usereusableagentdetection.ts","./src/features/channels/ui/useroutedmessageedit.ts","./src/features/channels/ui/usesearchhighlightprops.ts","./src/features/channels/ui/usethreadviewmodeswitch.ts","./src/features/channels/ui/usewelcomecomposerbanner.ts","./src/features/channels/ui/usewelcomeinitialunreadsuppression.ts","./src/features/chat/ui/chatheader.tsx","./src/features/communities/addcommunityprefill.ts","./src/features/communities/communityiconcache.ts","./src/features/communities/communitymarkread.ts","./src/features/communities/communitynavigationstorage.ts","./src/features/communities/communitystorage.ts","./src/features/communities/communityunreadobserver.ts","./src/features/communities/hostedcommunityapi.ts","./src/features/communities/leavecommunity.ts","./src/features/communities/legacycommunitystorage.ts","./src/features/communities/relayprobe.ts","./src/features/communities/types.ts","./src/features/communities/usecommunities.tsx","./src/features/communities/usecommunityicons.ts","./src/features/communities/usecommunityinit.ts","./src/features/communities/usecommunityunread.ts","./src/features/communities/usenestnotifications.ts","./src/features/communities/lib/downscaleicon.ts","./src/features/communities/ui/addcommunitydialog.tsx","./src/features/communities/ui/communityapplyerrorscreen.tsx","./src/features/communities/ui/communitychangeoverlay.tsx","./src/features/communities/ui/communityeditform.tsx","./src/features/communities/ui/communityiconsettingscard.tsx","./src/features/communities/ui/communityswitcher.tsx","./src/features/communities/ui/editcommunitydialog.tsx","./src/features/communities/ui/hostedcommunitycreateflow.tsx","./src/features/communities/ui/hostedcommunityonboarding.tsx","./src/features/communities/ui/welcomesetup.tsx","./src/features/community-members/hooks.ts","./src/features/community-members/usecommunityjoinalerts.ts","./src/features/community-members/lib/joinalerts.ts","./src/features/community-members/ui/addmemberdialog.tsx","./src/features/community-members/ui/communityinvitedialog.tsx","./src/features/community-members/ui/communitymemberscard.tsx","./src/features/community-members/ui/communitymemberssettingscard.tsx","./src/features/community-members/ui/confirmremovedialog.tsx","./src/features/community-members/ui/invitelinksection.tsx","./src/features/custom-emoji/emojimartcategory.ts","./src/features/custom-emoji/hooks.ts","./src/features/custom-emoji/ui/customemojisettingscard.tsx","./src/features/custom-emoji/ui/emojipicker.tsx","./src/features/custom-emoji/ui/emojimartprewarm.ts","./src/features/forum/hooks.ts","./src/features/forum/lib/time.ts","./src/features/forum/ui/deleteactionmenu.tsx","./src/features/forum/ui/deleteconfirmdialog.tsx","./src/features/forum/ui/forumcomposer.tsx","./src/features/forum/ui/forumcomposer.types.ts","./src/features/forum/ui/forumcomposerautocompletes.tsx","./src/features/forum/ui/forumcomposercompactlayout.tsx","./src/features/forum/ui/forumcomposermediastatus.tsx","./src/features/forum/ui/forumpostcard.tsx","./src/features/forum/ui/forumthreadpanel.tsx","./src/features/forum/ui/forumview.tsx","./src/features/forum/ui/usecompactcomposerinteractions.ts","./src/features/gifs/api.ts","./src/features/gifs/relay.ts","./src/features/gifs/ui/klipygifpicker.tsx","./src/features/home/hiddendminboxaction.ts","./src/features/home/hooks.ts","./src/features/home/usefeeditemstate.ts","./src/features/home/usehiddendminboxnavigation.ts","./src/features/home/usehomedrafts.ts","./src/features/home/usehomeinboxautoselection.ts","./src/features/home/usehomeinboxcontextmessages.ts","./src/features/home/usehomeinboxreadstate.ts","./src/features/home/usehomepersonalinbox.ts","./src/features/home/useinboxeditmessage.ts","./src/features/home/useinboxselectionanchor.ts","./src/features/home/useinboxthreadcontext.ts","./src/features/home/useownedagentpubkeys.ts","./src/features/home/useresizableinboxlistwidth.ts","./src/features/home/lib/homemessagecapabilities.ts","./src/features/home/lib/homepanelayout.ts","./src/features/home/lib/inbox.ts","./src/features/home/lib/inboxlistrows.ts","./src/features/home/lib/inboxselection.ts","./src/features/home/lib/inboxviewhelpers.ts","./src/features/home/lib/projectinbox.ts","./src/features/home/ui/feedsection.tsx","./src/features/home/ui/homeloadingstate.tsx","./src/features/home/ui/homememberssidebaroverlay.tsx","./src/features/home/ui/homepersonalinboxdetail.tsx","./src/features/home/ui/homescreen.tsx","./src/features/home/ui/homeview.tsx","./src/features/home/ui/inboxdetailpane.tsx","./src/features/home/ui/inboxfiltermenu.tsx","./src/features/home/ui/inboxlistpane.tsx","./src/features/home/ui/inboxmessagerow.tsx","./src/features/home/ui/projectinboxdetail.tsx","./src/features/home/ui/projectinboxdetailpane.tsx","./src/features/home/ui/recentnotessection.tsx","./src/features/huddle/huddlecontext.tsx","./src/features/huddle/huddlecontext.types.ts","./src/features/huddle/index.ts","./src/features/huddle/components/addagentdialog.tsx","./src/features/huddle/components/agentvoicemenu.tsx","./src/features/huddle/components/huddleattachment.tsx","./src/features/huddle/components/huddlebar.tsx","./src/features/huddle/components/huddleindicator.tsx","./src/features/huddle/components/huddleprofilecontrol.tsx","./src/features/huddle/components/huddleroomheader.tsx","./src/features/huddle/components/huddlestartingview.tsx","./src/features/huddle/components/huddletranscriptintro.tsx","./src/features/huddle/components/miccontrols.tsx","./src/features/huddle/components/participantlist.tsx","./src/features/huddle/hooks/usehuddleparticipantroster.ts","./src/features/huddle/lib/audioworklet.ts","./src/features/huddle/lib/huddlecardstate.ts","./src/features/huddle/lib/huddlechannelname.ts","./src/features/huddle/lib/huddleerror.ts","./src/features/huddle/lib/huddlewindow.ts","./src/features/huddle/lib/ttslivemessages.ts","./src/features/huddle/lib/useaudiodevices.ts","./src/features/huddle/lib/usehuddlepttstate.ts","./src/features/huddle/lib/usehuddlespeakeractivity.ts","./src/features/huddle/lib/usemiclevelanalyser.ts","./src/features/huddle/lib/usepipelinehotstart.ts","./src/features/huddle/lib/usettssubscription.ts","./src/features/identity-archive/hooks.ts","./src/features/local-archive/agentmetricarchivepreference.ts","./src/features/local-archive/observerarchivepreference.ts","./src/features/local-archive/useagentmetricarchiveseed.ts","./src/features/local-archive/usearchiveagentmetricsbridge.ts","./src/features/local-archive/usearchivesync.ts","./src/features/local-archive/useobserverarchiveseed.ts","./src/features/local-archive/ui/localarchivesettingscard.tsx","./src/features/local-archive/ui/localarchivekinds.ts","./src/features/mesh-compute/classifymodelref.ts","./src/features/mesh-compute/servingusage.ts","./src/features/mesh-compute/sharetogglestate.ts","./src/features/mesh-compute/hooks/usemeshdownloadprogress.ts","./src/features/mesh-compute/hooks/usemeshnodestatus.ts","./src/features/mesh-compute/hooks/usemeshservingusage.ts","./src/features/mesh-compute/ui/meshcomputesettingscard.tsx","./src/features/messages/hooks.ts","./src/features/messages/types.ts","./src/features/messages/usechanneltyping.ts","./src/features/messages/usefetcholdermessages.ts","./src/features/messages/useindependentthreadpanel.ts","./src/features/messages/useloadmissingancestors.ts","./src/features/messages/usethreadreplies.ts","./src/features/messages/usetypingbroadcast.ts","./src/features/messages/lib/agentaddressmention.d.mts","./src/features/messages/lib/agentmentionrevalidation.ts","./src/features/messages/lib/agentsnapshotclipboard.ts","./src/features/messages/lib/applyedittagoverlay.d.mts","./src/features/messages/lib/autopinmentionedagentspreference.ts","./src/features/messages/lib/auxbackfill.ts","./src/features/messages/lib/backgroundmediauploadphase.ts","./src/features/messages/lib/backgroundmediauploadstore.ts","./src/features/messages/lib/buildmentioncandidates.ts","./src/features/messages/lib/canmanagemessage.ts","./src/features/messages/lib/cansendtochannel.ts","./src/features/messages/lib/channelheadcache.ts","./src/features/messages/lib/channellink.ts","./src/features/messages/lib/channelwindowreconciliation.ts","./src/features/messages/lib/channelwindowresponse.ts","./src/features/messages/lib/channelwindowstore.ts","./src/features/messages/lib/codeblockextensions.ts","./src/features/messages/lib/composermessagelinknode.ts","./src/features/messages/lib/customemojinode.ts","./src/features/messages/lib/dateformatters.ts","./src/features/messages/lib/dmthreadagentmentionerror.ts","./src/features/messages/lib/draftmentionrefs.ts","./src/features/messages/lib/effectiveexplicitagentpubkeys.ts","./src/features/messages/lib/extractmentionpersonas.ts","./src/features/messages/lib/extractmentionpubkeys.ts","./src/features/messages/lib/flushmentiondebounce.ts","./src/features/messages/lib/formattimelinemessages.ts","./src/features/messages/lib/getvisibleagentaddresspubkeys.ts","./src/features/messages/lib/hasmention.ts","./src/features/messages/lib/imetamediamarkdown.ts","./src/features/messages/lib/imetaslots.ts","./src/features/messages/lib/inboxreplymigration.ts","./src/features/messages/lib/independentthreadpanel.ts","./src/features/messages/lib/linkeditorfocus.ts","./src/features/messages/lib/linkinteractionextension.ts","./src/features/messages/lib/linkpastetrailingspace.ts","./src/features/messages/lib/linkpreviewcontent.ts","./src/features/messages/lib/linkpreviewpreparationstore.ts","./src/features/messages/lib/mentioncandidates.ts","./src/features/messages/lib/mentioncodecontext.ts","./src/features/messages/lib/mentionhighlightextension.ts","./src/features/messages/lib/mentionmemberpubkeys.ts","./src/features/messages/lib/mentionranking.ts","./src/features/messages/lib/mentionsuggestionmapping.ts","./src/features/messages/lib/messagegrouping.ts","./src/features/messages/lib/messagelink.ts","./src/features/messages/lib/messagelinklabel.ts","./src/features/messages/lib/messagelinkmetadata.ts","./src/features/messages/lib/messagementionpubkeys.ts","./src/features/messages/lib/messagemerge.ts","./src/features/messages/lib/messagequerykeys.ts","./src/features/messages/lib/messagerowequality.ts","./src/features/messages/lib/messagethreadpanellayout.ts","./src/features/messages/lib/normalizementionclipboard.ts","./src/features/messages/lib/openpopoverlink.ts","./src/features/messages/lib/ordermentionpubkeys.ts","./src/features/messages/lib/pageoldermessages.ts","./src/features/messages/lib/parsediff.ts","./src/features/messages/lib/persistentagentaudience.ts","./src/features/messages/lib/plaintextprojection.ts","./src/features/messages/lib/projectchannelwindow.ts","./src/features/messages/lib/recentmentionpubkeys.ts","./src/features/messages/lib/remarkchanneldeeplinks.ts","./src/features/messages/lib/remarkentitylinks.ts","./src/features/messages/lib/remarkmessagelinks.ts","./src/features/messages/lib/renderscopedreactions.ts","./src/features/messages/lib/resolvelinkat.ts","./src/features/messages/lib/rowheightestimate.ts","./src/features/messages/lib/selectionblockformatting.ts","./src/features/messages/lib/sendtochannelsemantics.ts","./src/features/messages/lib/sentfromthread.ts","./src/features/messages/lib/snapshotsharedby.ts","./src/features/messages/lib/spoilerformatting.ts","./src/features/messages/lib/spoilermark.ts","./src/features/messages/lib/stripimplicitagentmentions.ts","./src/features/messages/lib/systemeventcopy.ts","./src/features/messages/lib/threadpanel.ts","./src/features/messages/lib/threadreplyhighlight.ts","./src/features/messages/lib/threadtreelayout.ts","./src/features/messages/lib/threading.ts","./src/features/messages/lib/timelineimagepreload.ts","./src/features/messages/lib/timelineitems.ts","./src/features/messages/lib/timelineloadingstate.ts","./src/features/messages/lib/timelinesnapshot.ts","./src/features/messages/lib/unreadmarker.ts","./src/features/messages/lib/useactiveagentpubkeys.ts","./src/features/messages/lib/useattachmentediting.ts","./src/features/messages/lib/usechannellinks.ts","./src/features/messages/lib/usecomposerautofocus.ts","./src/features/messages/lib/usecomposercustomemoji.ts","./src/features/messages/lib/usecomposermessagelinks.ts","./src/features/messages/lib/usecomposerspoilerparticles.ts","./src/features/messages/lib/usedefaultagentsuggestion.ts","./src/features/messages/lib/usedraftmentionrouting.ts","./src/features/messages/lib/usedraftrootstatus.ts","./src/features/messages/lib/usedrafts.ts","./src/features/messages/lib/useemojiautocomplete.ts","./src/features/messages/lib/usefilepicker.ts","./src/features/messages/lib/uselinkeditor.tsx","./src/features/messages/lib/usemediaupload.ts","./src/features/messages/lib/usementionselection.ts","./src/features/messages/lib/usementions.ts","./src/features/messages/lib/usemessageemoji.ts","./src/features/messages/lib/userenderscopedreactionhydration.ts","./src/features/messages/lib/userichtexteditor.ts","./src/features/messages/lib/usethreadfollows.ts","./src/features/messages/lib/videofiletype.ts","./src/features/messages/lib/videoreviewcontext.ts","./src/features/messages/lib/virtualizedtimelineitems.ts","./src/features/messages/lib/wavemessage.ts","./src/features/messages/ui/botidenticon.tsx","./src/features/messages/ui/channelautocomplete.tsx","./src/features/messages/ui/channelintroblock.tsx","./src/features/messages/ui/composeractivityaccessory.tsx","./src/features/messages/ui/composeraddresscontrols.tsx","./src/features/messages/ui/composerattachments.tsx","./src/features/messages/ui/composerdockbackdrop.tsx","./src/features/messages/ui/composerdocktoolbar.tsx","./src/features/messages/ui/composeremojipicker.tsx","./src/features/messages/ui/composerimageeditor.tsx","./src/features/messages/ui/composerreplyeditbanner.tsx","./src/features/messages/ui/composeruploadprogressoverlay.tsx","./src/features/messages/ui/composeruploadprogresspill.tsx","./src/features/messages/ui/daydivider.tsx","./src/features/messages/ui/deletemessageconfirmdialog.tsx","./src/features/messages/ui/diffmessage.tsx","./src/features/messages/ui/diffmessageexpanded.tsx","./src/features/messages/ui/diffviewer.tsx","./src/features/messages/ui/directmessageintroavatarstack.tsx","./src/features/messages/ui/draftdetailpane.tsx","./src/features/messages/ui/draftspanel.tsx","./src/features/messages/ui/emojiautocomplete.tsx","./src/features/messages/ui/formattingtoolbar.tsx","./src/features/messages/ui/mentionautocomplete.tsx","./src/features/messages/ui/messageactionbar.tsx","./src/features/messages/ui/messageagentaddressprefix.tsx","./src/features/messages/ui/messageagentowner.tsx","./src/features/messages/ui/messagecomposer.tsx","./src/features/messages/ui/messagecomposer.types.ts","./src/features/messages/ui/messagecomposertoolbar.tsx","./src/features/messages/ui/messageheader.tsx","./src/features/messages/ui/messagereactions.tsx","./src/features/messages/ui/messagerow.tsx","./src/features/messages/ui/messagethreadpanel.tsx","./src/features/messages/ui/messagethreadpanelskeleton.tsx","./src/features/messages/ui/messagethreadreplystate.tsx","./src/features/messages/ui/messagethreadrow.tsx","./src/features/messages/ui/messagethreadsummaryrow.tsx","./src/features/messages/ui/messagethreadtranscript.tsx","./src/features/messages/ui/messagetimeline.tsx","./src/features/messages/ui/messagetimelineerrorcard.tsx","./src/features/messages/ui/messagetimestamp.tsx","./src/features/messages/ui/newmessageresultrow.tsx","./src/features/messages/ui/newmessagescreen.tsx","./src/features/messages/ui/nonmembermentiondialog.tsx","./src/features/messages/ui/selectionformattingtray.tsx","./src/features/messages/ui/sentfromthreadline.tsx","./src/features/messages/ui/systemmessageavatars.tsx","./src/features/messages/ui/systemmessagerow.tsx","./src/features/messages/ui/timelinemessagelist.tsx","./src/features/messages/ui/timelinemessagerow.tsx","./src/features/messages/ui/timelinerowshell.tsx","./src/features/messages/ui/timelineskeleton.tsx","./src/features/messages/ui/typingindicatorrow.tsx","./src/features/messages/ui/unreaddivider.tsx","./src/features/messages/ui/wavemessageattachment.tsx","./src/features/messages/ui/anchoredscrollpolicy.ts","./src/features/messages/ui/confignudgeauthpubkey.ts","./src/features/messages/ui/draftsubmitkey.ts","./src/features/messages/ui/messagecomposerautosubmit.ts","./src/features/messages/ui/selectionformattingtrayeditordom.ts","./src/features/messages/ui/submitmessageedit.ts","./src/features/messages/ui/timelineretention.ts","./src/features/messages/ui/useactivepreparedlinkpreviews.ts","./src/features/messages/ui/useaddressmentionpulse.ts","./src/features/messages/ui/useaddressedagentmentionrestore.ts","./src/features/messages/ui/useagentaddresslockpicker.ts","./src/features/messages/ui/usealwaysaddressshortcut.ts","./src/features/messages/ui/useanchoredscroll.ts","./src/features/messages/ui/useautopinmentionedagents.ts","./src/features/messages/ui/usebufferedtimelinemessages.ts","./src/features/messages/ui/usecommittedemptytimeline.ts","./src/features/messages/ui/usecomposerattachmentspoilers.ts","./src/features/messages/ui/usecomposercontentstate.ts","./src/features/messages/ui/usecomposerheightpadding.ts","./src/features/messages/ui/usecomposerlinkpreviews.tsx","./src/features/messages/ui/usecomposermentionpicker.ts","./src/features/messages/ui/usecomposerpastehandler.ts","./src/features/messages/ui/usedraftpersistsnapshot.ts","./src/features/messages/ui/useimplicitagentmentionprovenance.ts","./src/features/messages/ui/useloadolderonscroll.ts","./src/features/messages/ui/usementionsendflow.helpers.ts","./src/features/messages/ui/usementionsendflow.ts","./src/features/messages/ui/usementionsendflow.types.ts","./src/features/messages/ui/usenewmessagerecipients.ts","./src/features/messages/ui/usequickreactionemojis.ts","./src/features/messages/ui/usereactionhandler.ts","./src/features/messages/ui/usesettlegatedprependmessages.ts","./src/features/messages/ui/usestablesendtochannel.ts","./src/features/messages/ui/usetimelineretention.ts","./src/features/messages/ui/useupwardpaginationwheel.ts","./src/features/messages/ui/usevirtualizedbottomsettle.ts","./src/features/messages/ui/usevirtualizedviewportresize.ts","./src/features/moderation/hooks.ts","./src/features/moderation/lib/moderationdm.ts","./src/features/moderation/lib/relayself.ts","./src/features/moderation/lib/restrictionstate.ts","./src/features/moderation/lib/timeout.ts","./src/features/moderation/lib/timeoutstore.ts","./src/features/moderation/ui/composertimeoutbanner.tsx","./src/features/moderation/ui/messagemoderationmenuitems.tsx","./src/features/moderation/ui/reportmessagedialog.tsx","./src/features/moderation/ui/timeoutdurationsubmenu.tsx","./src/features/notifications/hooks.ts","./src/features/notifications/use-feed-desktop-notifications.ts","./src/features/notifications/usenotificationsendername.ts","./src/features/notifications/lib/desktop.ts","./src/features/notifications/lib/feed.ts","./src/features/notifications/lib/homebadge.ts","./src/features/notifications/lib/notificationformat.ts","./src/features/notifications/lib/sendername.ts","./src/features/notifications/lib/shouldnotify.ts","./src/features/notifications/lib/sound.ts","./src/features/notifications/lib/target.ts","./src/features/onboarding/communityonboarding.tsx","./src/features/onboarding/devfreshonboarding.ts","./src/features/onboarding/hooks.ts","./src/features/onboarding/machineonboarding.ts","./src/features/onboarding/useclaiminvite.ts","./src/features/onboarding/usewelcomekickoffentrance.ts","./src/features/onboarding/usewelcomekickoffstage.ts","./src/features/onboarding/usewelcomekickoffstagepresence.tsx","./src/features/onboarding/welcome.ts","./src/features/onboarding/welcomecanvas.ts","./src/features/onboarding/welcomeguide.ts","./src/features/onboarding/welcomekickoff.ts","./src/features/onboarding/lib/encryptedbackup.ts","./src/features/onboarding/lib/keyimportinput.ts","./src/features/onboarding/ui/avatarstep.tsx","./src/features/onboarding/ui/backuppasswordtimeline.tsx","./src/features/onboarding/ui/backupstep.tsx","./src/features/onboarding/ui/backuptestflow.tsx","./src/features/onboarding/ui/communityonboardingflow.tsx","./src/features/onboarding/ui/defaultconfigstep.tsx","./src/features/onboarding/ui/downloadkeystep.tsx","./src/features/onboarding/ui/encryptedbackupcreator.tsx","./src/features/onboarding/ui/harnessmarks.tsx","./src/features/onboarding/ui/identitykeyhelpdialog.tsx","./src/features/onboarding/ui/identityrecoverypairing.tsx","./src/features/onboarding/ui/inviteredeemform.tsx","./src/features/onboarding/ui/joinpolicynotice.tsx","./src/features/onboarding/ui/keyringlockedscreen.tsx","./src/features/onboarding/ui/landingbees.tsx","./src/features/onboarding/ui/machineonboardingflow.tsx","./src/features/onboarding/ui/membershipdenied.tsx","./src/features/onboarding/ui/nostrkeyimportform.tsx","./src/features/onboarding/ui/nsecmaskeddisplay.tsx","./src/features/onboarding/ui/onboardingchrome.tsx","./src/features/onboarding/ui/onboardingflow.tsx","./src/features/onboarding/ui/onboardingfooter.tsx","./src/features/onboarding/ui/onboardingslidetransition.tsx","./src/features/onboarding/ui/pendinginvitegate.tsx","./src/features/onboarding/ui/profilestep.tsx","./src/features/onboarding/ui/recoveryscreen.tsx","./src/features/onboarding/ui/relaunchrequiredscreen.tsx","./src/features/onboarding/ui/resetfailedscreen.tsx","./src/features/onboarding/ui/runtimeerrortooltip.tsx","./src/features/onboarding/ui/runtimeicon.tsx","./src/features/onboarding/ui/setupstep.tsx","./src/features/onboarding/ui/welcomekickoffstage.tsx","./src/features/onboarding/ui/agentreadiness.ts","./src/features/onboarding/ui/onboardingruntimeselection.ts","./src/features/onboarding/ui/types.ts","./src/features/presence/hooks.ts","./src/features/presence/lib/presence.ts","./src/features/presence/lib/presencesubscriptionreconciler.ts","./src/features/presence/ui/presencebadge.tsx","./src/features/profile/avatarpresentationstore.ts","./src/features/profile/avatarprofilesync.ts","./src/features/profile/hooks.ts","./src/features/profile/profilecachesync.ts","./src/features/profile/useavatarupload.ts","./src/features/profile/lib/animatedavatarcapture.ts","./src/features/profile/lib/identity.ts","./src/features/profile/lib/nostrbindcallback.ts","./src/features/profile/lib/nostridentitybinding.ts","./src/features/profile/lib/profileactivityagent.ts","./src/features/profile/lib/profileactivitycarousel.ts","./src/features/profile/lib/profileactivityfeedscope.ts","./src/features/profile/lib/selfprofilestorage.ts","./src/features/profile/lib/usecanonicalmanagedagentprofile.ts","./src/features/profile/lib/useownedmanagedagentpersonaid.ts","./src/features/profile/lib/usercandidatesearch.ts","./src/features/profile/lib/userlabelstorage.ts","./src/features/profile/ui/animatedavatarbackdroppanel.tsx","./src/features/profile/ui/animatedavatarcameracontrols.tsx","./src/features/profile/ui/animatedavatarcamerapicker.tsx","./src/features/profile/ui/animatedavatarcapture.helpers.ts","./src/features/profile/ui/animatedavatarcapture.tsx","./src/features/profile/ui/animatedavatarcapture.types.ts","./src/features/profile/ui/animatedavatarcontrols.tsx","./src/features/profile/ui/animatedavatarreviewnav.tsx","./src/features/profile/ui/archiveconfirmdialog.tsx","./src/features/profile/ui/avatarcustomcolorpanel.tsx","./src/features/profile/ui/avatarupload.tsx","./src/features/profile/ui/maskedavatarbadgeframe.tsx","./src/features/profile/ui/nostrbindconsentdialog.tsx","./src/features/profile/ui/profileavatar.tsx","./src/features/profile/ui/profileavatareditor.helpers.ts","./src/features/profile/ui/profileavatareditor.tsx","./src/features/profile/ui/profileavatareditor.types.ts","./src/features/profile/ui/profileavatareditor.utils.ts","./src/features/profile/ui/profileavatarmodetabs.tsx","./src/features/profile/ui/profileavataruploadpreview.tsx","./src/features/profile/ui/profileavatarwithstatus.tsx","./src/features/profile/ui/profileinstancessection.tsx","./src/features/profile/ui/profilepopover.tsx","./src/features/profile/ui/profiletabcontenttransition.tsx","./src/features/profile/ui/selectedrecipientchip.tsx","./src/features/profile/ui/userprofileagentactions.tsx","./src/features/profile/ui/userprofileagentmanagementrows.tsx","./src/features/profile/ui/userprofileeditagentdialog.tsx","./src/features/profile/ui/userprofilepanel.tsx","./src/features/profile/ui/userprofilepanelagentdetails.tsx","./src/features/profile/ui/userprofilepaneldeletion.ts","./src/features/profile/ui/userprofilepanelfields.tsx","./src/features/profile/ui/userprofilepanelfocusedviews.tsx","./src/features/profile/ui/userprofilepanelframe.tsx","./src/features/profile/ui/userprofilepanelheadercontent.tsx","./src/features/profile/ui/userprofilepanelpersonasubmit.ts","./src/features/profile/ui/userprofilepanelsections.tsx","./src/features/profile/ui/userprofilepaneltabs.tsx","./src/features/profile/ui/userprofilepanelutils.ts","./src/features/profile/ui/userprofilepersonadialogs.tsx","./src/features/profile/ui/userprofilepopover.tsx","./src/features/profile/ui/userprofileprimaryactions.tsx","./src/features/profile/ui/userprofilesnapshotexportdialog.tsx","./src/features/profile/ui/useagentlifecycleactions.ts","./src/features/profile/ui/useprofileeditagentrequest.ts","./src/features/profile/ui/useprofileinteractionactions.ts","./src/features/projects/assignmentoperationfetch.ts","./src/features/projects/branchmutations.ts","./src/features/projects/createproject.ts","./src/features/projects/hooks.ts","./src/features/projects/issueassignments.ts","./src/features/projects/issuemutations.ts","./src/features/projects/projectactivity.d.mts","./src/features/projects/projectchannelcreation.ts","./src/features/projects/projectchannelrequest.ts","./src/features/projects/projectchannelrequestqueue.ts","./src/features/projects/projectcreation.ts","./src/features/projects/projectdeletion.ts","./src/features/projects/projectdeletionmutation.ts","./src/features/projects/projectenumeration.ts","./src/features/projects/projectfetch.ts","./src/features/projects/projectissues.d.mts","./src/features/projects/projectmodels.ts","./src/features/projects/projectownercontrol.ts","./src/features/projects/projectpullrequestconflictrecovery.ts","./src/features/projects/projectpullrequests.d.mts","./src/features/projects/projectrepositorycreation.ts","./src/features/projects/projectroutes.ts","./src/features/projects/projectsnapshot.ts","./src/features/projects/projecttaskcategories.ts","./src/features/projects/projectworkitems.ts","./src/features/projects/pullrequestmutations.ts","./src/features/projects/pullrequestreviews.ts","./src/features/projects/reposynchooks.ts","./src/features/projects/repositoryactivityhooks.ts","./src/features/projects/useaddprojectchannel.ts","./src/features/projects/useaddprojectrepository.ts","./src/features/projects/useattachprojectrepository.ts","./src/features/projects/usebindprojectrepositorychannel.ts","./src/features/projects/usecreateproject.ts","./src/features/projects/usegitidentity.ts","./src/features/projects/usehealprojecthomerepositories.ts","./src/features/projects/useoptimisticprojectbranches.ts","./src/features/projects/useprojectchannelrequests.ts","./src/features/projects/useprojectcommitdiff.ts","./src/features/projects/useprojectownerprofiles.ts","./src/features/projects/useprojectrepohost.ts","./src/features/projects/useprojectrepositoryrefselection.ts","./src/features/projects/useprojectrepositorysnapshots.ts","./src/features/projects/useprojectsreposnapshots.ts","./src/features/projects/userepositoryaccess.ts","./src/features/projects/lib/discussionchannels.ts","./src/features/projects/lib/projectagentconversation.ts","./src/features/projects/lib/projectagentconversationstorage.ts","./src/features/projects/lib/projectagentselection.ts","./src/features/projects/lib/projectbrancherrors.ts","./src/features/projects/lib/projectbranches.ts","./src/features/projects/lib/projectcloneurl.ts","./src/features/projects/lib/projectcollection.ts","./src/features/projects/lib/projectcontributormatching.ts","./src/features/projects/lib/projectdetailagentcontext.ts","./src/features/projects/lib/projectdetailsearch.ts","./src/features/projects/lib/projectdetailselectionitem.ts","./src/features/projects/lib/projectexternalurl.ts","./src/features/projects/lib/projectgiterror.ts","./src/features/projects/lib/projecthomechannel.ts","./src/features/projects/lib/projecthomeselection.ts","./src/features/projects/lib/projecthomesummary.ts","./src/features/projects/lib/projecthometemplate.ts","./src/features/projects/lib/projecthomeworkspacesheet.ts","./src/features/projects/lib/projectlabels.ts","./src/features/projects/lib/projectlanguages.ts","./src/features/projects/lib/projectlocalrepos.ts","./src/features/projects/lib/projectpathdisplay.ts","./src/features/projects/lib/projectrelatedchannels.ts","./src/features/projects/lib/projectrepoavailability.ts","./src/features/projects/lib/projectrepohost.ts","./src/features/projects/lib/projectreviewdisplay.ts","./src/features/projects/lib/projectselection.ts","./src/features/projects/lib/projectsharelinks.ts","./src/features/projects/lib/projectsidebarmembership.ts","./src/features/projects/lib/projectsidebarmembershipsync.ts","./src/features/projects/lib/projectsactivitydigest.ts","./src/features/projects/lib/projectssearch.ts","./src/features/projects/lib/projectsviewhelpers.ts","./src/features/projects/lib/useprojectselection.tsx","./src/features/projects/lib/useprojectsidebarmembership.ts","./src/features/projects/ui/addprojectrepositorydialog.tsx","./src/features/projects/ui/agentcontextpayloadpreview.tsx","./src/features/projects/ui/attachprojectrepositorydialog.tsx","./src/features/projects/ui/copysharelinkmenuitem.tsx","./src/features/projects/ui/createissuedialog.tsx","./src/features/projects/ui/createprojectdialog.tsx","./src/features/projects/ui/createprojectformcontent.tsx","./src/features/projects/ui/createprojectformsettings.tsx","./src/features/projects/ui/createprojectissuedialog.tsx","./src/features/projects/ui/createprojectworkitemdialog.tsx","./src/features/projects/ui/createpullrequestdialog.tsx","./src/features/projects/ui/discussionchannels.tsx","./src/features/projects/ui/githubmark.tsx","./src/features/projects/ui/issueassigneesrow.tsx","./src/features/projects/ui/mergepullrequestbutton.tsx","./src/features/projects/ui/projectagentchatpanel.tsx","./src/features/projects/ui/projectagentcontextstrip.tsx","./src/features/projects/ui/projectagentselectioncomposerbanner.tsx","./src/features/projects/ui/projectagentsubmittedcontextpill.tsx","./src/features/projects/ui/projectauthoridentity.tsx","./src/features/projects/ui/projectbranchactiondialogs.tsx","./src/features/projects/ui/projectbranchdialogs.tsx","./src/features/projects/ui/projectbrowserdialog.tsx","./src/features/projects/ui/projectcards.tsx","./src/features/projects/ui/projectchannelhome.tsx","./src/features/projects/ui/projectchannelicon.tsx","./src/features/projects/ui/projectchannelmanagement.tsx","./src/features/projects/ui/projectchannelrequestdialog.tsx","./src/features/projects/ui/projectcommitcopybutton.tsx","./src/features/projects/ui/projectcommitdetailpanel.tsx","./src/features/projects/ui/projectcontextrail.tsx","./src/features/projects/ui/projectconversationpanel.tsx","./src/features/projects/ui/projectconversationpanelcontext.tsx","./src/features/projects/ui/projectcreationdialog.tsx","./src/features/projects/ui/projectdetailchrome.tsx","./src/features/projects/ui/projectdetailfeedpanels.tsx","./src/features/projects/ui/projectdetailmeta.tsx","./src/features/projects/ui/projectdetailrightpanel.tsx","./src/features/projects/ui/projectdetailscreen.tsx","./src/features/projects/ui/projectdetailsection.tsx","./src/features/projects/ui/projectdetailunavailablestate.tsx","./src/features/projects/ui/projectentitylistrow.tsx","./src/features/projects/ui/projecteventtypeicon.tsx","./src/features/projects/ui/projectfeedrow.tsx","./src/features/projects/ui/projecthomecodebasepanel.tsx","./src/features/projects/ui/projecthomecolumn.tsx","./src/features/projects/ui/projecthomecommitspanel.tsx","./src/features/projects/ui/projecthomecontextpanel.tsx","./src/features/projects/ui/projecthomeworkspacesheet.tsx","./src/features/projects/ui/projectissuecommenttimeline.tsx","./src/features/projects/ui/projectissuespanel.tsx","./src/features/projects/ui/projectlistrowmenu.tsx","./src/features/projects/ui/projectoriginreference.tsx","./src/features/projects/ui/projectoverviewpanel.tsx","./src/features/projects/ui/projectpanelstate.tsx","./src/features/projects/ui/projectprofileidentity.tsx","./src/features/projects/ui/projectpullrequestfileschangedpanel.tsx","./src/features/projects/ui/projectpullrequestinlinecomments.tsx","./src/features/projects/ui/projectpullrequestspanel.tsx","./src/features/projects/ui/projectreadmepanel.tsx","./src/features/projects/ui/projectrepositoryactionspanel.tsx","./src/features/projects/ui/projectrepositorylatestcommitrow.tsx","./src/features/projects/ui/projectrepositorymanagement.tsx","./src/features/projects/ui/projectrepositorypanel.tsx","./src/features/projects/ui/projectrepositorysource.tsx","./src/features/projects/ui/projectrepositoryunavailablestate.tsx","./src/features/projects/ui/projectrichcontent.tsx","./src/features/projects/ui/projectrightpanelcontrols.tsx","./src/features/projects/ui/projectsectionheader.tsx","./src/features/projects/ui/projectselectablegroup.tsx","./src/features/projects/ui/projectselectiondiscussaction.tsx","./src/features/projects/ui/projectstatusprogressicon.tsx","./src/features/projects/ui/projectworkitemcommunicationactions.tsx","./src/features/projects/ui/projectworkitemcontextactions.tsx","./src/features/projects/ui/projectworkitemcontextdetails.tsx","./src/features/projects/ui/projectworkitemgroup.tsx","./src/features/projects/ui/projectworkitemrow.tsx","./src/features/projects/ui/projectworkspacetablist.tsx","./src/features/projects/ui/projectworkspacetabs.tsx","./src/features/projects/ui/projectsactivityfeed.tsx","./src/features/projects/ui/projectsagentpromptpage.tsx","./src/features/projects/ui/projectscategorycreatedialogs.tsx","./src/features/projects/ui/projectschannelslist.tsx","./src/features/projects/ui/projectsissueslist.tsx","./src/features/projects/ui/projectslistheaderbar.tsx","./src/features/projects/ui/projectslistscopedropdown.tsx","./src/features/projects/ui/projectsoverviewchattoggle.tsx","./src/features/projects/ui/projectsoverviewchromeactions.tsx","./src/features/projects/ui/projectsoverviewcontextsheet.tsx","./src/features/projects/ui/projectsoverviewitems.tsx","./src/features/projects/ui/projectsoverviewpanel.tsx","./src/features/projects/ui/projectsoverviewrail.tsx","./src/features/projects/ui/projectspullrequestslist.tsx","./src/features/projects/ui/projectsscreen.tsx","./src/features/projects/ui/projectssectionsearch.tsx","./src/features/projects/ui/projectsselectioncountmenu.tsx","./src/features/projects/ui/projectstoolbar.tsx","./src/features/projects/ui/projectsview.tsx","./src/features/projects/ui/projectsworkitemtable.tsx","./src/features/projects/ui/projectsworkitemsloadnotice.tsx","./src/features/projects/ui/pullrequestmetarail.tsx","./src/features/projects/ui/pullrequestreviewcard.tsx","./src/features/projects/ui/pullrequestreviewersrow.tsx","./src/features/projects/ui/pullrequestspanelsurface.tsx","./src/features/projects/ui/repositorycards.tsx","./src/features/projects/ui/sharelinkbutton.tsx","./src/features/projects/ui/unavailableprojectrepositories.tsx","./src/features/projects/ui/buildprojectsviewagentcontext.ts","./src/features/projects/ui/projectcontextactionstyles.ts","./src/features/projects/ui/projectdetailhelpers.ts","./src/features/projects/ui/projectgiterrortoast.ts","./src/features/projects/ui/projectgridcardstyles.ts","./src/features/projects/ui/projectlistrowstyles.ts","./src/features/projects/ui/projectpanelstyles.ts","./src/features/projects/ui/projectrightpanelcontext.ts","./src/features/projects/ui/projectworkitemgroups.ts","./src/features/projects/ui/projectsoverviewcontext.ts","./src/features/projects/ui/projectssectionmeta.ts","./src/features/projects/ui/projectsviewworkitems.ts","./src/features/projects/ui/usecreateprojectformsettings.ts","./src/features/projects/ui/useopenprojectterminal.ts","./src/features/projects/ui/useprojectdetailcrumbs.ts","./src/features/projects/ui/useprojectdetailpeople.ts","./src/features/projects/ui/useprojectdiscussinchannel.ts","./src/features/projects/ui/useprojectpanelwidths.ts","./src/features/projects/ui/useprojectprofilepanel.ts","./src/features/projects/ui/useprojectrepositoryopenactions.ts","./src/features/projects/ui/useprojectrepositorypanel.ts","./src/features/projects/ui/useprojectsoverviewagentcontext.ts","./src/features/projects/ui/useprojectsscrollindicator.ts","./src/features/projects/ui/userepositoryfilecontent.ts","./src/features/projects/ui/userepositoryfilecontentsource.ts","./src/features/projects/ui/userepositoryfilesnavigation.ts","./src/features/projects/ui/useretainedprojectgitviews.ts","./src/features/pulse/hooks.ts","./src/features/pulse/lib/groupagentnotes.ts","./src/features/pulse/lib/noteactions.ts","./src/features/pulse/lib/projectcomments.ts","./src/features/pulse/lib/replies.ts","./src/features/pulse/lib/usenoteactions.ts","./src/features/pulse/ui/agentactivitycard.tsx","./src/features/pulse/ui/notecard.tsx","./src/features/pulse/ui/pulsescreen.tsx","./src/features/pulse/ui/pulsetabbar.tsx","./src/features/pulse/ui/pulseview.tsx","./src/features/reminders/hooks.ts","./src/features/reminders/useremindernotifications.ts","./src/features/reminders/lib/reminderfilters.ts","./src/features/reminders/lib/remindernavigation.ts","./src/features/reminders/lib/remindernotificationpoll.ts","./src/features/reminders/lib/reminderservice.ts","./src/features/reminders/lib/remindertypes.ts","./src/features/reminders/lib/timepresets.ts","./src/features/reminders/ui/remindmelaterdialog.tsx","./src/features/reminders/ui/remindmelaterprovider.tsx","./src/features/reminders/ui/reminderspanel.tsx","./src/features/reminders/ui/snoozemenu.tsx","./src/features/search/hooks.ts","./src/features/search/usesearchresults.ts","./src/features/search/lib/parsesearchoperators.ts","./src/features/search/lib/searchmatch.ts","./src/features/search/ui/highlightedsearchtext.tsx","./src/features/search/ui/searchpromptplaceholder.tsx","./src/features/search/ui/searchresultitem.tsx","./src/features/search/ui/searchscopecontrols.tsx","./src/features/search/ui/topbarsearch.tsx","./src/features/search/ui/usesearchmenukeyboardnavigation.ts","./src/features/settings/encryptedbackupprovider.tsx","./src/features/settings/sidebarupdatecard.tsx","./src/features/settings/updatechecker.tsx","./src/features/settings/updateindicator.tsx","./src/features/settings/sidebarupdatecardvisibility.ts","./src/features/settings/hooks/updaterprovider.tsx","./src/features/settings/hooks/use-updater.ts","./src/features/settings/hooks/usesendfeedback.ts","./src/features/settings/lib/appearancescopecopy.ts","./src/features/settings/lib/encryptedbackup.ts","./src/features/settings/lib/moderationqueue.ts","./src/features/settings/ui/agentdefaultssettingscard.tsx","./src/features/settings/ui/agentssettingspanel.tsx","./src/features/settings/ui/appearancesettingscontrols.tsx","./src/features/settings/ui/backuptestflow.tsx","./src/features/settings/ui/channeltemplatessettingscard.tsx","./src/features/settings/ui/customharnessform.tsx","./src/features/settings/ui/encryptedbackupcreator.tsx","./src/features/settings/ui/experimentalfeaturescard.tsx","./src/features/settings/ui/harnesscatalogdialog.tsx","./src/features/settings/ui/harnessrow.tsx","./src/features/settings/ui/harnessessettingspanel.tsx","./src/features/settings/ui/hostedcommunitiessettingscard.tsx","./src/features/settings/ui/keyboardshortcutscard.tsx","./src/features/settings/ui/mobilepairingcard.tsx","./src/features/settings/ui/moderationqueuecard.tsx","./src/features/settings/ui/notificationsettingscard.tsx","./src/features/settings/ui/preventsleepsettingscard.tsx","./src/features/settings/ui/privatekeybackuprow.tsx","./src/features/settings/ui/profilesettingscard.tsx","./src/features/settings/ui/sendfeedbackcontroller.tsx","./src/features/settings/ui/sendfeedbackdialog.tsx","./src/features/settings/ui/settingsoptiongroup.tsx","./src/features/settings/ui/settingspanels.tsx","./src/features/settings/ui/settingsscreen.tsx","./src/features/settings/ui/settingssectionheader.tsx","./src/features/settings/ui/settingsview.tsx","./src/features/settings/ui/signoutsection.tsx","./src/features/settings/ui/soundpicker.tsx","./src/features/settings/ui/voicesettingscard.tsx","./src/features/settings/ui/harnesscatalogcopy.ts","./src/features/settings/ui/harnesscataloglogic.ts","./src/features/settings/ui/harnessformlogic.ts","./src/features/settings/ui/harnessgallerylogic.ts","./src/features/settings/ui/voicesettingslogic.ts","./src/features/sidebar/usedmsidebarmetadata.ts","./src/features/sidebar/data/sidebardata.ts","./src/features/sidebar/lib/channellabels.ts","./src/features/sidebar/lib/channelmutesstorage.ts","./src/features/sidebar/lib/channelmutessync.ts","./src/features/sidebar/lib/channelsectionshelpers.ts","./src/features/sidebar/lib/channelsectionsstorage.ts","./src/features/sidebar/lib/channelsectionssync.ts","./src/features/sidebar/lib/channelsortpreference.ts","./src/features/sidebar/lib/channelsortsync.ts","./src/features/sidebar/lib/channelstarsstorage.ts","./src/features/sidebar/lib/channelstarssync.ts","./src/features/sidebar/lib/dmsidebarsort.ts","./src/features/sidebar/lib/sidebarbackgroundtarget.ts","./src/features/sidebar/lib/sidebarsyncwatermark.ts","./src/features/sidebar/lib/useactiveworkingchannelsbyid.ts","./src/features/sidebar/lib/usechannelmutes.ts","./src/features/sidebar/lib/usechannelsections.ts","./src/features/sidebar/lib/usechannelsortpreference.ts","./src/features/sidebar/lib/usechannelstars.ts","./src/features/sidebar/lib/usecreatechannelform.ts","./src/features/sidebar/lib/useoffscreenactivitychannelids.ts","./src/features/sidebar/lib/usesidebaractivityoverflow.ts","./src/features/sidebar/lib/usesidebarscrolllock.ts","./src/features/sidebar/lib/useunreadoverflow.ts","./src/features/sidebar/ui/appsidebar.tsx","./src/features/sidebar/ui/appsidebar.types.ts","./src/features/sidebar/ui/appsidebarpinnedheader.tsx","./src/features/sidebar/ui/channelactivitypopover.tsx","./src/features/sidebar/ui/channelcontextmenu.tsx","./src/features/sidebar/ui/channelsectiondialogs.tsx","./src/features/sidebar/ui/communityrail.tsx","./src/features/sidebar/ui/createchanneldialog.tsx","./src/features/sidebar/ui/createchannelformfields.tsx","./src/features/sidebar/ui/customchannelsection.tsx","./src/features/sidebar/ui/moreunreadbutton.tsx","./src/features/sidebar/ui/sidebardnd.tsx","./src/features/sidebar/ui/sidebarprofilecard.tsx","./src/features/sidebar/ui/sidebarprojectssection.tsx","./src/features/sidebar/ui/sidebarrelayconnectioncard.tsx","./src/features/sidebar/ui/sidebarsection.tsx","./src/features/sidebar/ui/listsidebarprojects.ts","./src/features/sidebar/ui/sidebarloadingskeleton.tsx","./src/features/sidebar/ui/sidebarmenuhelpers.tsx","./src/features/sidebar/ui/sidebarsectionstyles.ts","./src/features/sidebar/ui/usesidebarrelayconnectioncard.ts","./src/features/terminal/terminalbootstrap.tsx","./src/features/terminal/terminalsubstrate.tsx","./src/features/terminal/fadecontroller.ts","./src/features/terminal/terminalbanner.ts","./src/features/terminal/terminalbannercolor.ts","./src/features/terminal/terminalbannerpainter.ts","./src/features/terminal/terminalbannerwave.ts","./src/features/terminal/terminalclient.ts","./src/features/terminal/terminalpanelstore.ts","./src/features/terminal/terminalrenderer.ts","./src/features/terminal/terminalstate.ts","./src/features/user-status/hooks.ts","./src/features/user-status/ui/setstatusdialog.tsx","./src/features/user-status/ui/statusemoji.tsx","./src/features/workflows/hooks.ts","./src/features/workflows/ui/channelcombobox.tsx","./src/features/workflows/ui/cronexpressioninput.tsx","./src/features/workflows/ui/workflowactionsmenu.tsx","./src/features/workflows/ui/workflowapprovalcard.tsx","./src/features/workflows/ui/workflowauthorpicker.tsx","./src/features/workflows/ui/workflowcard.tsx","./src/features/workflows/ui/workflowdeletedialog.tsx","./src/features/workflows/ui/workflowdetailpanel.tsx","./src/features/workflows/ui/workflowdialog.tsx","./src/features/workflows/ui/workflowdurationfield.tsx","./src/features/workflows/ui/workfloweditorhost.tsx","./src/features/workflows/ui/workflowemojifield.tsx","./src/features/workflows/ui/workflowformbuilder.tsx","./src/features/workflows/ui/workflowmessagepicker.tsx","./src/features/workflows/ui/workflowmessagetextconditioneditor.tsx","./src/features/workflows/ui/workflowrichtriggerdescription.tsx","./src/features/workflows/ui/workflowruntrace.tsx","./src/features/workflows/ui/workflowschedulefields.tsx","./src/features/workflows/ui/workflowstepcard.tsx","./src/features/workflows/ui/workflowtemplatetextarea.tsx","./src/features/workflows/ui/workflowtriggerconditions.tsx","./src/features/workflows/ui/workflowunavailabledialog.tsx","./src/features/workflows/ui/workflowwebhookheaderseditor.tsx","./src/features/workflows/ui/workflowwebhooksecretdialog.tsx","./src/features/workflows/ui/workflowsscreen.tsx","./src/features/workflows/ui/workflowsview.tsx","./src/features/workflows/ui/cronexpression.ts","./src/features/workflows/ui/useworkflowauthorpresentation.ts","./src/features/workflows/ui/useworkflowlistauthorpresentations.ts","./src/features/workflows/ui/useworkflowlistmessagepresentations.ts","./src/features/workflows/ui/useworkflowtriggerpresentation.ts","./src/features/workflows/ui/workflowactivationwarning.ts","./src/features/workflows/ui/workflowauthorcandidates.ts","./src/features/workflows/ui/workflowconditionexpression.ts","./src/features/workflows/ui/workflowdefinition.ts","./src/features/workflows/ui/workflowduration.ts","./src/features/workflows/ui/workfloweditorpane.ts","./src/features/workflows/ui/workflowformprimitives.tsx","./src/features/workflows/ui/workflowformtypes.ts","./src/features/workflows/ui/workflowmessagecandidates.ts","./src/features/workflows/ui/workflowmessagetextcondition.ts","./src/features/workflows/ui/workflowreactioncondition.ts","./src/features/workflows/ui/workflowschedule.ts","./src/features/workflows/ui/workflowstepdescription.ts","./src/features/workflows/ui/workflowtemplatevariables.ts","./src/features/workflows/ui/workflowtriggerdescription.ts","./src/features/workflows/ui/workflowyamldocument.ts","./src/shared/deep-link.ts","./src/shared/useappdeeplinks.ts","./src/shared/useentitydeeplinks.ts","./src/shared/usemessagedeeplinks.ts","./src/shared/api/agentcontrol.ts","./src/shared/api/agentmodels.ts","./src/shared/api/channelreconnectrepair.ts","./src/shared/api/channelwindow.ts","./src/shared/api/communityprofile.ts","./src/shared/api/concurrency.ts","./src/shared/api/customemoji.ts","./src/shared/api/editmessage.ts","./src/shared/api/forum.ts","./src/shared/api/hooks.ts","./src/shared/api/identitytypes.ts","./src/shared/api/installtypes.ts","./src/shared/api/invitehelpers.ts","./src/shared/api/invites.ts","./src/shared/api/moderation.ts","./src/shared/api/observerrelay.ts","./src/shared/api/osidle.ts","./src/shared/api/presencerelaysubscription.ts","./src/shared/api/projectgit.ts","./src/shared/api/projectgittypes.ts","./src/shared/api/queryclient.ts","./src/shared/api/readonlyrelayclient.ts","./src/shared/api/relayauthpolicy.ts","./src/shared/api/relaychannelfilters.ts","./src/shared/api/relayclient.ts","./src/shared/api/relayclientsession.ts","./src/shared/api/relayclientshared.ts","./src/shared/api/relayclienttimings.ts","./src/shared/api/relayclosedpolicy.ts","./src/shared/api/relayclosedrecovery.ts","./src/shared/api/relayconnectionstateemitter.ts","./src/shared/api/relaygateboundary.ts","./src/shared/api/relayinboundbuffer.ts","./src/shared/api/relaymembers.ts","./src/shared/api/relayqueryinvalidation.ts","./src/shared/api/relayratelimitgate.ts","./src/shared/api/relayreconnectcontroller.ts","./src/shared/api/relayreconnectpolicy.ts","./src/shared/api/relayreconnectreplay.ts","./src/shared/api/relayreconnectwaiters.ts","./src/shared/api/relayresumetriggerpolicy.ts","./src/shared/api/relaystallwatchdog.ts","./src/shared/api/relaywebsocketclose.ts","./src/shared/api/restartdiff.ts","./src/shared/api/searchtypes.ts","./src/shared/api/social.ts","./src/shared/api/socialtypes.ts","./src/shared/api/tauri.ts","./src/shared/api/tauriacpdiscovery.ts","./src/shared/api/tauriagentaccess.ts","./src/shared/api/tauriagentauth.ts","./src/shared/api/tauriarchive.ts","./src/shared/api/taurichannelheadcache.ts","./src/shared/api/taurichanneltemplates.ts","./src/shared/api/taurichannels.ts","./src/shared/api/tauriengrams.ts","./src/shared/api/taurievents.ts","./src/shared/api/tauriglobalagentconfig.ts","./src/shared/api/tauriidentity.ts","./src/shared/api/tauriidentityarchive.ts","./src/shared/api/taurimanagedagentmessagemarkers.ts","./src/shared/api/taurimanagedagentmessages.ts","./src/shared/api/taurimanagedagents.ts","./src/shared/api/taurimedia.ts","./src/shared/api/taurimesh.ts","./src/shared/api/taurimessagetypes.ts","./src/shared/api/taurimessages.ts","./src/shared/api/tauriobservedunread.ts","./src/shared/api/tauriobserver.ts","./src/shared/api/tauripairing.ts","./src/shared/api/tauripersonas.ts","./src/shared/api/tauriprofiles.ts","./src/shared/api/taurirelayagents.ts","./src/shared/api/tauriteams.ts","./src/shared/api/tauriunreadcatchup.ts","./src/shared/api/tauriworkflows.ts","./src/shared/api/teamtypes.ts","./src/shared/api/traymenu.ts","./src/shared/api/types.ts","./src/shared/api/usereconnectrelay.ts","./src/shared/api/userelayautoheal.ts","./src/shared/api/userelayconnection.ts","./src/shared/api/userelayresumetriggers.ts","./src/shared/api/workflowtypes.ts","./src/shared/constants/brand.ts","./src/shared/constants/kinds.ts","./src/shared/context/agentsessioncontext.tsx","./src/shared/context/channelnavigationcontext.tsx","./src/shared/context/profilepanelcontext.tsx","./src/shared/context/workfloweditoroverlaycontext.tsx","./src/shared/features/featuregate.tsx","./src/shared/features/index.ts","./src/shared/features/manifest.ts","./src/shared/features/resolveenabled.ts","./src/shared/features/store.ts","./src/shared/features/types.ts","./src/shared/features/usefeatureenabled.ts","./src/shared/hooks/escapesurfaces.ts","./src/shared/hooks/use-mobile.tsx","./src/shared/hooks/usedeferredstartup.ts","./src/shared/hooks/useescapekey.ts","./src/shared/hooks/usefileimportzone.ts","./src/shared/hooks/usehistorysearchstate.ts","./src/shared/hooks/useincrementalmount.ts","./src/shared/hooks/usepanelreturntarget.ts","./src/shared/hooks/usescrollboundarylock.ts","./src/shared/hooks/usestablereference.ts","./src/shared/hooks/usethreadpanelwidth.ts","./src/shared/hooks/usetoasteffect.ts","./src/shared/hooks/usewebviewscrollboundarylock.ts","./src/shared/layout/auxiliarypanelbody.tsx","./src/shared/layout/auxiliarypanelheader.tsx","./src/shared/layout/auxiliarypanelshell.tsx","./src/shared/layout/maininsetcontext.tsx","./src/shared/layout/topchromeinsetheader.tsx","./src/shared/layout/auxiliarypanelcontext.ts","./src/shared/layout/auxiliarypanellayout.ts","./src/shared/layout/chromelayout.ts","./src/shared/layout/observeelementblocksize.ts","./src/shared/layout/sidebarlayout.ts","./src/shared/layout/usemeasuredcssvariable.ts","./src/shared/layout/auxiliarypanel/index.ts","./src/shared/lib/animatedavatar.ts","./src/shared/lib/authors.ts","./src/shared/lib/clipboard.ts","./src/shared/lib/cn.ts","./src/shared/lib/codeblockclipboard.ts","./src/shared/lib/computeconfignudge.ts","./src/shared/lib/confignudge.ts","./src/shared/lib/conversationdensitypreference.ts","./src/shared/lib/createremarkprefixplugin.ts","./src/shared/lib/customemojitags.ts","./src/shared/lib/datetime.ts","./src/shared/lib/detectprefixquery.ts","./src/shared/lib/emojiname.ts","./src/shared/lib/emojionly.ts","./src/shared/lib/emojisearch.ts","./src/shared/lib/entitylink.ts","./src/shared/lib/eventlookuperror.ts","./src/shared/lib/fontsizepreference.ts","./src/shared/lib/foregroundready.ts","./src/shared/lib/fuzzytext.ts","./src/shared/lib/haptics.ts","./src/shared/lib/initials.ts","./src/shared/lib/installerror.ts","./src/shared/lib/keyboard-shortcuts.ts","./src/shared/lib/linkpreview.ts","./src/shared/lib/linkpreviewsnapshot.ts","./src/shared/lib/linkpreviewstylepreference.ts","./src/shared/lib/localstoragequota.ts","./src/shared/lib/localstoragesweep.ts","./src/shared/lib/maskedlink.ts","./src/shared/lib/mediaurl.ts","./src/shared/lib/mentionpattern.ts","./src/shared/lib/normalizerelayurl.ts","./src/shared/lib/nostrutils.ts","./src/shared/lib/panelreturntarget.ts","./src/shared/lib/platform.ts","./src/shared/lib/promptpreview.ts","./src/shared/lib/pubkey.ts","./src/shared/lib/rehypeimagegallery.ts","./src/shared/lib/rehypeleadinginlinecontent.ts","./src/shared/lib/rehypesearchhighlight.ts","./src/shared/lib/relayerror.ts","./src/shared/lib/remarkchannellinks.ts","./src/shared/lib/remarkcustomemoji.ts","./src/shared/lib/remarkmentions.ts","./src/shared/lib/remarkspoilers.ts","./src/shared/lib/resolvementionnames.ts","./src/shared/lib/rosterderivations.ts","./src/shared/lib/safestorage.ts","./src/shared/lib/searchhighlightstyle.ts","./src/shared/lib/titlebaractions.ts","./src/shared/lib/trailingdebounce.ts","./src/shared/lib/trimmaptosize.ts","./src/shared/lib/url.ts","./src/shared/lib/usedocumentvisible.ts","./src/shared/lib/useisfullscreen.ts","./src/shared/lib/usemediaproxyport.ts","./src/shared/lib/usenow.ts","./src/shared/lib/userelayorigin.ts","./src/shared/lib/useresolvedlinkpreviews.ts","./src/shared/theme/communitythemecontroller.tsx","./src/shared/theme/themepreviewframe.tsx","./src/shared/theme/themeprovider.tsx","./src/shared/theme/adaptive-theme.ts","./src/shared/theme/communitythemepreference.ts","./src/shared/theme/communitythemesync.ts","./src/shared/theme/terminal-palette.ts","./src/shared/theme/theme-loader.ts","./src/shared/theme/usesystemcolorscheme.ts","./src/shared/theme/usethemepreviewvars.ts","./src/shared/ui/animatedcount.tsx","./src/shared/ui/buzzloadingstate.tsx","./src/shared/ui/drawerpanelicon.tsx","./src/shared/ui/emojiburstprovider.tsx","./src/shared/ui/hovercopyindicator.tsx","./src/shared/ui/inlinechip.tsx","./src/shared/ui/overlaypanelbackdrop.tsx","./src/shared/ui/pageheader.tsx","./src/shared/ui/panelsectiongroup.tsx","./src/shared/ui/poofburstprovider.tsx","./src/shared/ui/portalledscrollarea.tsx","./src/shared/ui/pubkey.tsx","./src/shared/ui/shimmer.tsx","./src/shared/ui/simpleimagelightbox.tsx","./src/shared/ui/spoilerparticles.tsx","./src/shared/ui/startupwindowdragregion.tsx","./src/shared/ui/terminalpanelicon.tsx","./src/shared/ui/topchromebackdrop.tsx","./src/shared/ui/unreadpill.tsx","./src/shared/ui/useravatar.tsx","./src/shared/ui/videoplayer.tsx","./src/shared/ui/videoreviewcommentmarkdown.tsx","./src/shared/ui/videoreviewnavigation.tsx","./src/shared/ui/videoreviewposterpreview.tsx","./src/shared/ui/videoreviewtimecodebutton.tsx","./src/shared/ui/viewloadingfallback.tsx","./src/shared/ui/virtualizedlist.tsx","./src/shared/ui/alert-dialog.tsx","./src/shared/ui/alert.tsx","./src/shared/ui/animatedcountparts.ts","./src/shared/ui/attachment.tsx","./src/shared/ui/avatar.tsx","./src/shared/ui/badge.tsx","./src/shared/ui/button.tsx","./src/shared/ui/card.tsx","./src/shared/ui/carousel.tsx","./src/shared/ui/checkbox.tsx","./src/shared/ui/chooser-dialog-content.tsx","./src/shared/ui/compact-link-preview-attachment.tsx","./src/shared/ui/config-nudge-attachment.tsx","./src/shared/ui/context-menu.tsx","./src/shared/ui/deferredmodalopen.ts","./src/shared/ui/dialog.tsx","./src/shared/ui/dropdown-menu.tsx","./src/shared/ui/icons.ts","./src/shared/ui/identity-card-skeleton.tsx","./src/shared/ui/import-status-icon.tsx","./src/shared/ui/input.tsx","./src/shared/ui/link-preview-attachment.tsx","./src/shared/ui/link-preview-controls.tsx","./src/shared/ui/link-preview-list.tsx","./src/shared/ui/markdown.tsx","./src/shared/ui/markdownfilecard.ts","./src/shared/ui/markdownutils.ts","./src/shared/ui/mentionchip.ts","./src/shared/ui/modalbackdrop.ts","./src/shared/ui/modalmotion.ts","./src/shared/ui/modalsearchstyles.ts","./src/shared/ui/popover.tsx","./src/shared/ui/popoversurface.ts","./src/shared/ui/progress.tsx","./src/shared/ui/rich-link-preview-attachment.tsx","./src/shared/ui/segmented-control.tsx","./src/shared/ui/separator.tsx","./src/shared/ui/sheet.tsx","./src/shared/ui/sidebar-action-card.tsx","./src/shared/ui/sidebar-menu-label.tsx","./src/shared/ui/sidebar.tsx","./src/shared/ui/skeleton.tsx","./src/shared/ui/smoothcorners.ts","./src/shared/ui/sonner.tsx","./src/shared/ui/spinner.tsx","./src/shared/ui/step-progress.tsx","./src/shared/ui/styled-qr-code.tsx","./src/shared/ui/switch.tsx","./src/shared/ui/tabs.tsx","./src/shared/ui/textarea.tsx","./src/shared/ui/toggle.tsx","./src/shared/ui/tooltip.tsx","./src/shared/ui/usevideocontextmenu.tsx","./src/shared/ui/videoaspectratio.ts","./src/shared/ui/videodownload.ts","./src/shared/ui/videoplayerstate.ts","./src/shared/ui/videoreviewtimecode.ts","./src/shared/ui/buzz-logo/buzzlogoanimation.tsx","./src/shared/ui/buzz-logo/buzzmark.tsx","./src/shared/ui/buzz-logo/flappingbee.tsx","./src/shared/ui/buzz-logo/fuzzylogo.tsx","./src/shared/ui/markdown/agentsnapshotcard.tsx","./src/shared/ui/markdown/buzzlinkchip.tsx","./src/shared/ui/markdown/channeldeeplink.tsx","./src/shared/ui/markdown/codeblock.tsx","./src/shared/ui/markdown/externallinkanchor.tsx","./src/shared/ui/markdown/filecard.tsx","./src/shared/ui/markdown/imagegallerystatus.tsx","./src/shared/ui/markdown/imagelightboxzoomcontrols.tsx","./src/shared/ui/markdown/imagemosaic.tsx","./src/shared/ui/markdown/inlineemojipopover.tsx","./src/shared/ui/markdown/linkpreviewimagelightbox.tsx","./src/shared/ui/markdown/markdowninput.tsx","./src/shared/ui/markdown/markdowntable.tsx","./src/shared/ui/markdown/markdownvideoplayer.tsx","./src/shared/ui/markdown/maskedlinktooltip.tsx","./src/shared/ui/markdown/mediacontextmenu.tsx","./src/shared/ui/markdown/messagelinkpill.tsx","./src/shared/ui/markdown/progressiveimage.tsx","./src/shared/ui/markdown/spoilerinline.tsx","./src/shared/ui/markdown/entitylinks.tsx","./src/shared/ui/markdown/imagelightbox.ts","./src/shared/ui/markdown/mediaentry.ts","./src/shared/ui/markdown/nodecache.ts","./src/shared/ui/markdown/parseimeta.ts","./src/shared/ui/markdown/runtimecontext.ts","./src/shared/ui/markdown/types.ts","./src/shared/ui/markdown/useinlinetooltipposition.ts","./src/shared/ui/markdown/usemessagelinkmetadata.ts","./src/shared/ui/markdown/usemessagelinkpreviews.ts","./src/shared/ui/markdown/utils.ts","./src/testing/e2ebridge.ts","./src/testing/e2ebridgecustomharnesses.ts","./src/types/qrcode.d.ts"],"errors":true,"version":"6.0.3"} \ No newline at end of file diff --git a/docker-compose.relay.yml b/docker-compose.relay.yml new file mode 100644 index 00000000000..c520cb2eaee --- /dev/null +++ b/docker-compose.relay.yml @@ -0,0 +1,82 @@ +# Runs buzz-relay as a container alongside the dev stack in docker-compose.yml, +# so the relay starts automatically with Docker Desktop and needs no terminal. +# +# Usage: +# docker compose -f docker-compose.yml -f docker-compose.relay.yml up -d +# +# Deliberately NOT deploy/compose/compose.yml: that bundle brings its own +# Postgres/Redis/MinIO on the same ports with separate volumes, which would +# shadow the dev database (community rows, channels, identity). This override +# adds only the relay and points it at the dev services already holding that +# data. +# +# Stop the host-run relay before starting this -- both bind port 3000. +# +# env_file supplies BUZZ_RELAY_PRIVATE_KEY (so the relay keeps its identity +# across host/container runs) and everything else from .env. The `environment` +# block below then overrides the infra URLs: .env points at 127.0.0.1 for host +# processes, but inside the compose network the services resolve by name. +# Compose gives `environment` precedence over `env_file`. + +name: buzz + +services: + relay: + image: ghcr.io/block/buzz:main + container_name: buzz-relay + env_file: + - .env + environment: + BUZZ_BIND_ADDR: 0.0.0.0:3000 + BUZZ_HEALTH_PORT: "8080" + BUZZ_METRICS_PORT: "9102" + DATABASE_URL: postgres://buzz:buzz_dev@postgres:5432/buzz + PGHOST: postgres + # The dev Redis runs without a password, unlike deploy/compose. + REDIS_URL: redis://redis:6379 + BUZZ_S3_ENDPOINT: http://minio:9000 + # Docker DNS resolves `minio`, not `.minio`, so addressing must be path-style. + BUZZ_S3_ADDRESSING_STYLE: path + BUZZ_S3_ACCESS_KEY: buzz_dev + BUZZ_S3_SECRET_KEY: buzz_dev_secret + BUZZ_S3_BUCKET: buzz-media + BUZZ_GIT_REPO_PATH: /data/git + RELAY_URL: ws://localhost:3000 + # Migrations are owned by `cargo run -p buzz-admin -- migrate` on the host. + BUZZ_AUTO_MIGRATE: "false" + ports: + - "3000:3000" + - "127.0.0.1:8080:8080" + - "127.0.0.1:9102:9102" + volumes: + - git-data:/data/git + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + minio: + condition: service_healthy + # The runtime image has bash but no curl/wget, so probe over /dev/tcp. + healthcheck: + test: + [ + "CMD-SHELL", + "bash -ec 'exec 3<>/dev/tcp/127.0.0.1/8080; printf \"GET /_readiness HTTP/1.1\\r\\nHost: 127.0.0.1\\r\\nConnection: close\\r\\n\\r\\n\" >&3; grep -q \"200 OK\" <&3'", + ] + interval: 10s + timeout: 3s + retries: 12 + start_period: 30s + restart: unless-stopped + networks: + - buzz-net + labels: + com.buzz.service: "relay" + com.buzz.env: "dev" + +volumes: + git-data: + name: buzz-git-data + labels: + com.buzz.volume: "git" diff --git a/scripts/build-windows-installer.bat b/scripts/build-windows-installer.bat new file mode 100644 index 00000000000..a600ff73c7b --- /dev/null +++ b/scripts/build-windows-installer.bat @@ -0,0 +1,85 @@ +@echo off +setlocal +REM ============================================================================ +REM build-windows-installer.bat -- produce an installable Buzz.exe for Windows +REM ============================================================================ +REM Usage: scripts\build-windows-installer.bat +REM +REM Builds the release sidecars, copies them where Tauri expects, then runs +REM `pnpm tauri build` to produce an NSIS installer (and MSI where WiX is +REM available). Output lands in: +REM desktop\src-tauri\target\release\bundle\nsis\Buzz__x64-setup.exe +REM +REM Takes considerably longer than a debug build (full optimization across the +REM whole dependency graph). Unattended once started. +REM +REM See scripts\dev-windows.bat for why vcvars64 is required (Git's +REM /usr/bin/link.exe otherwise shadows the MSVC linker) and for the VS 2022 +REM v143 requirement. The same "(x86)" parenthesis trap applies here, so this +REM script also branches with goto rather than parenthesized if-blocks. +REM +REM Note: bundling reads tauri.conf.json merged with tauri.windows.conf.json. +REM The latter drops the buzz-backend-kubernetes sidecar, which is not built on +REM Windows -- so only the five sidecars below are needed. +REM ============================================================================ + +set "REPO_ROOT=%~dp0.." +pushd "%REPO_ROOT%" || exit /b 1 + +set "TARGET=x86_64-pc-windows-msvc" + +set "VSDEVCMD=C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvars64.bat" +if exist "%VSDEVCMD%" goto :have_vs +echo [build-installer] ERROR: VS 2022 Build Tools not found at: +echo "%VSDEVCMD%" +popd +exit /b 1 + +:have_vs +call "%VSDEVCMD%" >nul +if errorlevel 1 goto :vs_failed +goto :vs_ok + +:vs_failed +echo [build-installer] ERROR: vcvars64.bat failed. +popd +exit /b 1 + +:vs_ok +where /q cmake.exe || set "PATH=C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin;%PATH%" + +REM ---- Release sidecars ------------------------------------------------------ + +echo [build-installer] Building release sidecars ... +cargo build --release -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p buzz-cli -p git-credential-nostr +if errorlevel 1 goto :sidecar_failed +goto :sidecars_ok + +:sidecar_failed +echo [build-installer] ERROR: sidecar build failed. +popd +exit /b 1 + +:sidecars_ok +if not exist "desktop\src-tauri\binaries" mkdir "desktop\src-tauri\binaries" +for %%b in (buzz-acp buzz-agent buzz-dev-mcp git-credential-nostr buzz) do ( + copy /y "target\release\%%b.exe" "desktop\src-tauri\binaries\%%b-%TARGET%.exe" >nul + if errorlevel 1 echo [build-installer] WARNING: could not copy %%b.exe +) + +REM ---- Bundle ---------------------------------------------------------------- + +cd /d "%REPO_ROOT%\desktop" +echo [build-installer] Running tauri build ^(this is the long part^) ... +call pnpm tauri build +set "EXIT_CODE=%ERRORLEVEL%" + +if not "%EXIT_CODE%"=="0" goto :done +echo. +echo [build-installer] Installer(s) written to: +dir /b /s "%REPO_ROOT%\desktop\src-tauri\target\release\bundle\*.exe" 2>nul +dir /b /s "%REPO_ROOT%\desktop\src-tauri\target\release\bundle\*.msi" 2>nul + +:done +popd +exit /b %EXIT_CODE% diff --git a/scripts/dev-windows.bat b/scripts/dev-windows.bat new file mode 100644 index 00000000000..0b2375d296a --- /dev/null +++ b/scripts/dev-windows.bat @@ -0,0 +1,173 @@ +@echo off +setlocal EnableDelayedExpansion +REM ============================================================================ +REM dev-windows.bat -- Windows equivalent of `just dev` +REM ============================================================================ +REM Usage: scripts\dev-windows.bat +REM +REM Starts the relay, the desktop Vite dev server, and the Tauri desktop app. +REM Assumes `just setup` has already been done once (see "One-time setup" below). +REM +REM Why this exists instead of `just dev`: +REM +REM 1. `just dev` prepends the repo's bin/ (Hermit shims) to PATH. Hermit is +REM POSIX-only; on Windows those entries are checked out as plain text +REM files containing the symlink target, so they are not executable. +REM This script uses the natively installed toolchain instead. +REM +REM 2. `just dev`'s beforeDevCommand is Unix shell +REM ("exec ./node_modules/.bin/vite ..."), which cmd.exe cannot run. Vite +REM is started here as a separate process and Tauri is pointed at it. +REM +REM 3. rustc resolves a bare `link.exe` off PATH. Git for Windows ships +REM /usr/bin/link.exe (GNU coreutils `link`), which shadows the MSVC +REM linker and fails with "missing operand after '@...linker-arguments'". +REM Calling vcvars64.bat puts the MSVC toolchain ahead of it. +REM Do NOT set VCINSTALLDIR by hand to work around this: rustc then +REM believes it is already inside a developer prompt, skips vswhere +REM detection, and picks the Git linker again. +REM +REM Requirements: +REM - Docker Desktop running. The compose services are `restart: unless-stopped`, +REM so Postgres/Redis/MinIO/Adminer come back on their own once Docker is up; +REM run `docker compose up -d` only if they are missing. +REM - Visual Studio 2022 Build Tools with the C++ workload (MSVC v143). +REM v142 (VS 2019) cannot link this project: the prebuilt ONNX Runtime that +REM sherpa-onnx pulls in (huddle speech-to-text) references vectorized STL +REM symbols (__std_find_trivial_*, __std_max_element_*, ...) that only exist +REM in the v143 STL, producing ~41 unresolved externals. +REM - .env must point the infra URLs at 127.0.0.1, not localhost. On hosts +REM where localhost resolves to ::1 first, Docker's 127.0.0.1-only port +REM bindings are unreachable and migrations fail with a pool timeout. +REM RELAY_URL can stay localhost; seed-local-community.sh registers both. +REM +REM One-time setup (from Git Bash, once per clone): +REM cp .env.example .env +REM ./scripts/ensure-local-relay-key.sh .env +REM # point DATABASE_URL / PGHOST / REDIS_URL / BUZZ_S3_ENDPOINT at 127.0.0.1 +REM docker compose up -d postgres redis minio minio-init adminer +REM cargo run -p buzz-admin -- migrate +REM ./scripts/seed-local-community.sh +REM pnpm install +REM cargo build -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p buzz-cli -p git-credential-nostr +REM # then copy those into desktop/src-tauri/binaries/-x86_64-pc-windows-msvc.exe +REM ============================================================================ + +set "REPO_ROOT=%~dp0.." +pushd "%REPO_ROOT%" || exit /b 1 + +set "VITE_PORT=43137" +set "RELAY_URL_WS=ws://localhost:3000" + +REM ---- MSVC environment (must precede any cargo invocation) ------------------ + +REM NOTE: paths here contain "(x86)". Never echo them inside a parenthesized +REM block -- the ")" closes the block and cmd fails with +REM "\Microsoft was unexpected at this time." Hence the goto-based branching. + +set "VSDEVCMD=C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvars64.bat" +if exist "%VSDEVCMD%" goto :have_vs +echo [dev-windows] ERROR: VS 2022 Build Tools ^(MSVC v143^) not found at: +echo "%VSDEVCMD%" +echo. +echo Install the C++ workload with: +echo winget install --id Microsoft.VisualStudio.2022.BuildTools --override "--quiet --wait --norestart --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended" +popd +exit /b 1 + +:have_vs +call "%VSDEVCMD%" >nul +if errorlevel 1 goto :vs_failed +goto :vs_ok + +:vs_failed +echo [dev-windows] ERROR: vcvars64.bat failed. +popd +exit /b 1 + +:vs_ok + +REM CMake is needed by the audiopus_sys build script (Opus codec, huddle audio). +REM VS 2022 ships one; only prepend if cmake is not already resolvable. +where /q cmake.exe || set "PATH=C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin;%PATH%" + +REM ---- Preflight ------------------------------------------------------------- + +if exist ".env" goto :have_env +echo [dev-windows] ERROR: .env not found. See "One-time setup" in this script. +popd +exit /b 1 + +:have_env +docker info >nul 2>&1 +if errorlevel 1 goto :no_docker +goto :have_docker + +:no_docker +echo [dev-windows] ERROR: Docker daemon not reachable. Start Docker Desktop and retry. +popd +exit /b 1 + +:have_docker + +REM ---- Relay ----------------------------------------------------------------- +REM Load .env into this process (the relay inherits it), mirroring the +REM `set -o allexport; source .env` that the just recipes do. `eol=#` skips +REM comment lines; `tokens=1,* delims==` splits on the first `=` only, so +REM values containing `=` survive intact. + +echo [dev-windows] Loading .env ... +for /f "usebackq eol=# tokens=1,* delims==" %%a in ("%REPO_ROOT%\.env") do ( + if not "%%~a"=="" set "%%~a=%%~b" +) + +if exist "target\debug\buzz-relay.exe" goto :have_relay +echo [dev-windows] ERROR: target\debug\buzz-relay.exe not found. Build it first: +echo cargo build -p buzz-relay +popd +exit /b 1 + +:have_relay + +echo [dev-windows] Starting relay on %RELAY_URL_WS% ... +start "buzz-relay" cmd /c "cd /d "%REPO_ROOT%" && target\debug\buzz-relay.exe" + +REM ---- Vite ------------------------------------------------------------------ + +echo [dev-windows] Starting Vite dev server on http://localhost:%VITE_PORT% ... +start "buzz-vite" cmd /c "cd /d "%REPO_ROOT%\desktop" && pnpm exec vite --port %VITE_PORT% --strictPort" + +REM ---- Desktop app ----------------------------------------------------------- +REM Wait for the relay's readiness probe before launching, mirroring `just dev`, +REM so the app does not come up against a relay that is still migrating. + +echo [dev-windows] Waiting for relay readiness ... +set "RELAY_READY=" +for /l %%i in (1,1,120) do ( + if not defined RELAY_READY ( + curl --silent --fail --max-time 1 http://127.0.0.1:8080/_readiness >nul 2>&1 && set "RELAY_READY=1" + if not defined RELAY_READY ping -n 2 127.0.0.1 >nul + ) +) +if defined RELAY_READY goto :relay_ready +echo [dev-windows] ERROR: relay did not become ready within 60s; refusing to launch desktop. +echo Check the "buzz-relay" window for the failure. +popd +exit /b 1 + +:relay_ready +echo [dev-windows] Relay ready. + +set "TAURI_CONFIG=%TEMP%\buzz-dev-config.json" +> "%TAURI_CONFIG%" echo {"build":{"devUrl":"http://localhost:%VITE_PORT%","beforeDevCommand":""},"identifier":"xyz.block.buzz.app.dev","productName":"Buzz Dev"} + +set "BUZZ_RELAY_URL=%RELAY_URL_WS%" +set "BUZZ_DEV_KEYRING_SERVICE=buzz-desktop-dev.main" + +cd /d "%REPO_ROOT%\desktop" +echo [dev-windows] Launching desktop app ^(relay %BUZZ_RELAY_URL%, vite %VITE_PORT%^) ... +call pnpm exec tauri dev --config "%TAURI_CONFIG%" +set "EXIT_CODE=%ERRORLEVEL%" + +popd +exit /b %EXIT_CODE% diff --git a/web/index.html b/web/index.html index 0b9f358282e..76b7221c18e 100644 --- a/web/index.html +++ b/web/index.html @@ -3,7 +3,7 @@ - Buzz + Dreamforge diff --git a/web/src/features/invite/ui/InviteJoinPolicyNotice.tsx b/web/src/features/invite/ui/InviteJoinPolicyNotice.tsx index 585de0b898d..e3029011e35 100644 --- a/web/src/features/invite/ui/InviteJoinPolicyNotice.tsx +++ b/web/src/features/invite/ui/InviteJoinPolicyNotice.tsx @@ -113,11 +113,11 @@ export function InviteJoinPolicyNotice({ {policy.terms_markdown || policy.privacy_markdown ? ( - I agree to the Buzz{" "} + I agree to the Dreamforge{" "} {policy.terms_markdown ? (

    You're invited to @@ -247,7 +247,7 @@ export function InvitePage({ code }: { code: string }) { - Accept invite in Buzz + Accept invite in Dreamforge ) : ( @@ -260,7 +260,7 @@ export function InvitePage({ code }: { code: string }) { disabled={disabled} onClick={openInvite} > - Accept invite in Buzz + Accept invite in Dreamforge )} {browserJoinError ? ( diff --git a/web/src/features/repos/mock-repos.ts b/web/src/features/repos/mock-repos.ts index e05bd3440fd..3be65f8cd53 100644 --- a/web/src/features/repos/mock-repos.ts +++ b/web/src/features/repos/mock-repos.ts @@ -20,7 +20,7 @@ export const mockRepos: Repo[] = [ id: "buzz-desktop", name: "buzz-desktop", description: - "The desktop client for collaborating with people and agents across Buzz communities.", + "The desktop client for collaborating with people and agents across Dreamforge communities.", cloneUrls: ["https://example.com/buzz-desktop.git"], webUrl: null, channelId: null, @@ -115,7 +115,7 @@ export const mockRepoCommits: CommitInfo[] = [ export const mockRepoReadme: ReadmeResult = { filename: "README.md", content: - "# Buzz Desktop\n\nA focused community for people and agents to collaborate.\n\n## Getting started\n\nInstall dependencies, then start the development app.", + "# Dreamforge Desktop\n\nA focused community for people and agents to collaborate.\n\n## Getting started\n\nInstall dependencies, then start the development app.", }; export function getMockBlob( diff --git a/web/src/features/repos/ui/ConnectButton.tsx b/web/src/features/repos/ui/ConnectButton.tsx index 312955b9c02..8e51b0a29d4 100644 --- a/web/src/features/repos/ui/ConnectButton.tsx +++ b/web/src/features/repos/ui/ConnectButton.tsx @@ -13,7 +13,7 @@ export function ConnectButton({ className }: { className?: string }) { > - Open in Buzz + Open in Dreamforge ); diff --git a/web/src/features/repos/ui/OrgSidebar.tsx b/web/src/features/repos/ui/OrgSidebar.tsx index 1878a15239d..bcd43eb98cf 100644 --- a/web/src/features/repos/ui/OrgSidebar.tsx +++ b/web/src/features/repos/ui/OrgSidebar.tsx @@ -24,7 +24,7 @@ export function OrgSidebar({ repos }: { repos: Repo[] }) { return (
    - {/* Open in Buzz */} + {/* Open in Dreamforge */} {/* People section */} diff --git a/web/src/features/repos/ui/RepoDetailPage.tsx b/web/src/features/repos/ui/RepoDetailPage.tsx index baedb3aadf9..3e5dff8a847 100644 --- a/web/src/features/repos/ui/RepoDetailPage.tsx +++ b/web/src/features/repos/ui/RepoDetailPage.tsx @@ -386,7 +386,7 @@ export function RepoDetailPage() { {/* Sidebar */}
    From 931ec249777878a8618c816af39c9145e3a5f3e8 Mon Sep 17 00:00:00 2001 From: QuicksilverSlick Date: Mon, 31 Aug 2026 07:31:49 -0500 Subject: [PATCH 05/10] feat(brand): rename the three hyphenated display strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Buzz-native", "Buzz-hosted" and "Buzz-curated" survived the first sweep because the pattern excluded a trailing hyphen — deliberately, to keep wire tags like buzz-channel intact. These are safe: wire tags are lowercase, and a capital B never appears in one. All three are prose a person reads. Co-Authored-By: Claude Opus 5 --- desktop/src/features/local-archive/ui/localArchiveKinds.ts | 2 +- desktop/src/features/projects/ui/RepositoryCards.tsx | 2 +- desktop/src/testing/e2eBridge.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/desktop/src/features/local-archive/ui/localArchiveKinds.ts b/desktop/src/features/local-archive/ui/localArchiveKinds.ts index 0717559f71e..f61dee52eb0 100644 --- a/desktop/src/features/local-archive/ui/localArchiveKinds.ts +++ b/desktop/src/features/local-archive/ui/localArchiveKinds.ts @@ -68,7 +68,7 @@ function kindLabel(kind: number): string { case 9: return "Stream messages (kind 9)"; case 9005: - return "Buzz-native deletions (kind 9005)"; + return "Dreamforge-native deletions (kind 9005)"; case 40002: return "Stream messages v2 (kind 40002)"; case 40003: diff --git a/desktop/src/features/projects/ui/RepositoryCards.tsx b/desktop/src/features/projects/ui/RepositoryCards.tsx index f33de916165..ba574737dbc 100644 --- a/desktop/src/features/projects/ui/RepositoryCards.tsx +++ b/desktop/src/features/projects/ui/RepositoryCards.tsx @@ -67,7 +67,7 @@ function RepositoryHostIcon({ const host = projectRepoHostForRepository(repository, useRelayOrigin()); const label = host.kind === "buzz" - ? "Buzz-hosted repository" + ? "Dreamforge-hosted repository" : host.kind === "external" ? `Git data hosted on ${host.host}` : "Repository host"; diff --git a/desktop/src/testing/e2eBridge.ts b/desktop/src/testing/e2eBridge.ts index 8facc696b3e..a9adf470085 100644 --- a/desktop/src/testing/e2eBridge.ts +++ b/desktop/src/testing/e2eBridge.ts @@ -12087,7 +12087,7 @@ export function maybeInstallE2eTauriMocks() { name: "Gemma-4-E4B-it-Q4_K_M", size: "3.5GB", sizeGb: 3.5, - description: "Buzz-curated local agent model", + description: "Dreamforge-curated local agent model", fit: "comfortable", installed: true, recommended: true, From 918c96f0bacb372d56d415c89c93bcd1eb0ea635 Mon Sep 17 00:00:00 2001 From: QuicksilverSlick Date: Mon, 31 Aug 2026 07:59:26 -0500 Subject: [PATCH 06/10] feat(buzz-core): ticket kinds and the state fold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Foundation for durable tickets: a request is written down when it arrives so it cannot be silently dropped, and something outside the agent sweeps for ones that stopped moving. This commit is the part with no I/O — kinds and the fold — so the model can be reviewed before anything depends on it. TWO KINDS, NOT ONE KIND_TICKET 30623 the request KIND_TICKET_STATUS 30624 one live row per (ticket, actor) The NIP-33 replacement key is (community, kind, pubkey, d_tag), so the author pubkey is part of an addressable event's identity. A single addressable ticket could therefore only ever be advanced by its original signer — an assignee, a reviewer, or the watchdog could never move it. Splitting root from status turns that binding from an obstacle into the authorization mechanism: each actor owns their own row, and no actor can forge another's. WHY 3062x AND NOT THE EMPTY 47000 BLOCK Storage class is decided purely by numeric range, so a 4xxxx kind can never be replaced — every transition would be a new immutable row — and extract_d_tag returns None outside 30000–39999, leaving d_tag NULL and no indexed way to find all events for a ticket. 30623/30624 are the next free slots after 30620/30621/30622; verified free across .rs/.ts/.sql. Adding a kind to the registry does not make the relay accept it: required_scope_for_kind still ends in "restricted: unknown event kind", so ingest wiring is a separate, deliberate step. THE FOLD State is derived from status events rather than stored in a column, so the events stay canonical and a replayed log reproduces the same answer. Four rules, each with a test: - terminal wins, so one actor's stale Progress cannot keep a finished ticket alive; - otherwise the furthest-advanced live state wins; - ties break on recency; - the deadline is the EARLIEST live one, not the latest — a watchdog must fire on the first thing that should have happened, and taking the latest would let one long-running actor mask another's stall. Escalated is deliberately NOT terminal. Raising something to a human is not resolving it, and an escalation that is then ignored is exactly the case this exists to catch. Unknown `s` tag values are rejected rather than guessed: a ticket in an unrecognised state must not silently look healthy. 9 tests. fmt and clippy clean; buzz-core suite green at 271 passed. Co-Authored-By: Claude Opus 5 --- crates/buzz-core/src/kind.rs | 27 +++ crates/buzz-core/src/lib.rs | 3 + crates/buzz-core/src/ticket.rs | 334 +++++++++++++++++++++++++++++++++ 3 files changed, 364 insertions(+) create mode 100644 crates/buzz-core/src/ticket.rs diff --git a/crates/buzz-core/src/kind.rs b/crates/buzz-core/src/kind.rs index 3c6f1d5913d..4136072b70b 100644 --- a/crates/buzz-core/src/kind.rs +++ b/crates/buzz-core/src/kind.rs @@ -448,6 +448,29 @@ pub const KIND_WORKFLOW_DEF: u32 = 30620; /// `hidden_at` per viewer; this is the only Nostr-visible projection of it. pub const KIND_DM_VISIBILITY: u32 = 30622; +/// A durable request that must reach a terminal state (parameterized +/// replaceable, d=ticket_id). Authored by whoever made the request; the +/// content is theirs and nobody else can rewrite it, because the NIP-33 +/// replacement key includes the author pubkey. +/// +/// State does NOT live here — see [`KIND_TICKET_STATUS`]. A ticket exists so +/// that a request cannot be silently dropped: it is written down when it +/// arrives, and something outside the agent sweeps for ones that stopped +/// moving. +pub const KIND_TICKET: u32 = 30623; + +/// One live status row per (ticket, actor) (parameterized replaceable, +/// d=`{ticket_id}:{actor_pubkey}`, `e`=ticket root id). +/// +/// Split from [`KIND_TICKET`] because the NIP-33 replacement key is +/// `(community, kind, pubkey, d_tag)` — the author is part of the identity. A +/// single addressable ticket could therefore only ever be advanced by its +/// original signer, so an assignee, a reviewer, or the watchdog could never +/// move it. Splitting root from status turns that pubkey binding from an +/// obstacle into the authorization mechanism: each actor owns their own row +/// and no actor can forge another's. +pub const KIND_TICKET_STATUS: u32 = 30624; + /// Lower bound of the NIP-33 parameterized replaceable range (30000–39999). pub const PARAM_REPLACEABLE_KIND_MIN: u32 = 30000; /// Upper bound of the NIP-33 parameterized replaceable range (30000–39999). @@ -712,6 +735,8 @@ pub const ALL_KINDS: &[u32] = &[ KIND_CHANNEL_SUMMARY, KIND_PRESENCE_SNAPSHOT, KIND_DM_VISIBILITY, + KIND_TICKET, + KIND_TICKET_STATUS, KIND_DM_OPEN, KIND_DM_ADD_MEMBER, KIND_DM_HIDE, @@ -862,6 +887,8 @@ const _: () = assert!(is_parameterized_replaceable(KIND_WORKFLOW_DEF)); // 30620 const _: () = assert!(is_parameterized_replaceable(KIND_EVENT_REMINDER)); // 30300 ∈ 30000–39999 const _: () = assert!(is_parameterized_replaceable(KIND_DM_VISIBILITY)); // 30622 ∈ 30000–39999 const _: () = assert!(is_parameterized_replaceable(KIND_PROJECT)); // 30621 ∈ 30000–39999 +const _: () = assert!(is_parameterized_replaceable(KIND_TICKET)); // 30623 ∈ 30000–39999 +const _: () = assert!(is_parameterized_replaceable(KIND_TICKET_STATUS)); // 30624 ∈ 30000–39999 const _: () = assert!(is_parameterized_replaceable(KIND_THREAD_SUMMARY)); // 39005 ∈ 30000–39999 const _: () = assert!(is_parameterized_replaceable(KIND_WINDOW_BOUNDS)); // 39006 ∈ 30000–39999 diff --git a/crates/buzz-core/src/lib.rs b/crates/buzz-core/src/lib.rs index 36dc772da3b..ebb000fecd5 100644 --- a/crates/buzz-core/src/lib.rs +++ b/crates/buzz-core/src/lib.rs @@ -40,6 +40,9 @@ pub mod private_managed_agent; pub mod relay; /// Tenant identity — the server-resolved community key carried on scoped paths. pub mod tenant; + +/// Ticket state: the fold from status events to what is true now. +pub mod ticket; /// Schnorr signature and event ID verification. pub mod verification; diff --git a/crates/buzz-core/src/ticket.rs b/crates/buzz-core/src/ticket.rs new file mode 100644 index 00000000000..71322a09e91 --- /dev/null +++ b/crates/buzz-core/src/ticket.rs @@ -0,0 +1,334 @@ +//! Ticket state: the fold from status events to "what is true now". +//! +//! A ticket exists so a request cannot be silently dropped. It is written down +//! when it arrives, and something outside the agent sweeps for tickets that +//! stopped moving. This module is the part with no I/O: given a ticket's status +//! events, decide the current state and when the next deadline expires. +//! +//! # Why a fold rather than a stored status column +//! +//! State is derived from [`crate::kind::KIND_TICKET_STATUS`] events, one live +//! row per (ticket, actor). Deriving it means the events stay canonical — there +//! is no second copy to drift, and a replayed log always reproduces the same +//! answer. It also means an actor can only ever speak for themselves: a status +//! row is addressed by `(ticket, actor)`, and NIP-33 binds the author pubkey +//! into the replacement key, so nobody can forge another actor's row. +//! +//! # Why deadlines are part of the state +//! +//! Every non-terminal state carries the instant it must be acted on by. The +//! sweeper does not need to understand *why* a ticket is stuck — only that its +//! deadline passed. That is what lets it catch failures nobody enumerated in +//! advance, which is the majority of the ones that matter. + +use std::collections::BTreeMap; + +/// What an actor last said about a ticket. +/// +/// Ordering is deliberate: [`Ord`] ranks terminal states above live ones so a +/// fold can take the max without special-casing. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum TicketState { + /// Written down, nobody has picked it up. + Open, + /// Someone accepted it. Proves the request was not lost in transit. + Acknowledged, + /// Work is happening, renewed periodically. A stale Progress is the signal + /// that something hung — silence, not an error. + Progress, + /// Raised to a human because a deadline passed or the agent gave up. + Escalated, + /// Terminal: finished. + Done, + /// Terminal: gave up, with a reason the requester can read. + Failed, +} + +impl TicketState { + /// Whether no further action is expected. + /// + /// `Escalated` is deliberately NOT terminal: raising something to a human + /// is not the same as resolving it. A ticket that escalates and is then + /// ignored is exactly the case this system exists to catch. + pub const fn is_terminal(self) -> bool { + matches!(self, TicketState::Done | TicketState::Failed) + } + + /// Machine-readable form used in the `s` tag. + pub const fn as_tag(self) -> &'static str { + match self { + TicketState::Open => "open", + TicketState::Acknowledged => "ack", + TicketState::Progress => "progress", + TicketState::Escalated => "escalated", + TicketState::Done => "done", + TicketState::Failed => "failed", + } + } + + /// Parse an `s` tag value. Unknown values are rejected rather than guessed: + /// a ticket in an unrecognised state must not silently look healthy. + pub fn from_tag(s: &str) -> Option { + Some(match s { + "open" => TicketState::Open, + "ack" => TicketState::Acknowledged, + "progress" => TicketState::Progress, + "escalated" => TicketState::Escalated, + "done" => TicketState::Done, + "failed" => TicketState::Failed, + _ => return None, + }) + } +} + +/// One actor's latest word on a ticket. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StatusRow { + /// Hex pubkey of whoever published this row. + pub actor: String, + /// What this actor last said about the ticket. + pub state: TicketState, + /// Unix seconds the row was created. + pub created_at: i64, + /// Unix seconds by which this state must change, if it is not terminal. + pub not_before: Option, + /// Required when `state` is [`TicketState::Failed`] — a failure a person + /// cannot read is a failure they cannot act on. + pub reason: Option, +} + +/// The resolved state of one ticket. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TicketView { + /// The state the fold resolved to. + pub state: TicketState, + /// The deadline the sweeper watches. `None` once terminal. + pub deadline: Option, + /// Which actor's row decided `state`. + pub decided_by: Option, + /// Why it failed, carried through from the deciding row. + pub reason: Option, +} + +impl TicketView { + /// A ticket nobody has said anything about yet. + pub fn open(deadline: Option) -> Self { + Self { + state: TicketState::Open, + deadline, + decided_by: None, + reason: None, + } + } + + /// Whether `now` is past the deadline and the ticket still expects action. + pub fn is_overdue(&self, now: i64) -> bool { + !self.state.is_terminal() && self.deadline.is_some_and(|d| now >= d) + } +} + +/// Resolve a ticket's current state from every actor's status row. +/// +/// Rules, in order: +/// +/// 1. **Terminal wins.** If any actor says done or failed, the ticket is +/// finished. Anyone working on it has finished or given up, and a stale +/// Progress row from another actor must not keep it alive forever. +/// 2. **Otherwise the furthest-advanced live state wins**, so one actor still +/// reporting Progress keeps the ticket live even if another only ever +/// acknowledged it. +/// 3. **Ties break on recency**, so the most recent word wins. +/// 4. **The deadline is the EARLIEST** among live rows, not the latest. A +/// watchdog must fire on the first thing that should have happened; taking +/// the latest would let one long-running actor mask another's stall. +/// +/// `open_deadline` applies when there are no rows at all — the acknowledgement +/// deadline, which is what catches a request that never reached anyone. +pub fn fold(rows: &[StatusRow], open_deadline: Option) -> TicketView { + if rows.is_empty() { + return TicketView::open(open_deadline); + } + + // One row per actor: the latest they published. Rows arriving out of order + // must not resurrect an older state. + let mut latest: BTreeMap<&str, &StatusRow> = BTreeMap::new(); + for r in rows { + latest + .entry(r.actor.as_str()) + .and_modify(|cur| { + if (r.created_at, r.state) > (cur.created_at, cur.state) { + *cur = r; + } + }) + .or_insert(r); + } + + let terminal = latest + .values() + .filter(|r| r.state.is_terminal()) + .max_by_key(|r| (r.created_at, r.state)); + + if let Some(t) = terminal { + return TicketView { + state: t.state, + deadline: None, + decided_by: Some(t.actor.clone()), + reason: t.reason.clone(), + }; + } + + let decided = latest + .values() + .max_by_key(|r| (r.state, r.created_at)) + .expect("non-empty"); + + // Earliest live deadline: fire on the first thing that should have happened. + let deadline = latest + .values() + .filter(|r| !r.state.is_terminal()) + .filter_map(|r| r.not_before) + .min(); + + TicketView { + state: decided.state, + deadline, + decided_by: Some(decided.actor.clone()), + reason: decided.reason.clone(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn row(actor: &str, state: TicketState, at: i64, not_before: Option) -> StatusRow { + StatusRow { + actor: actor.to_string(), + state, + created_at: at, + not_before, + reason: None, + } + } + + #[test] + fn no_rows_is_open_and_carries_the_ack_deadline() { + // The case that matters most: a request nobody ever picked up. It must + // still have a deadline, or the commonest failure is the invisible one. + let v = fold(&[], Some(100)); + assert_eq!(v.state, TicketState::Open); + assert_eq!(v.deadline, Some(100)); + assert!(v.is_overdue(100), "overdue exactly at the deadline"); + assert!(!v.is_overdue(99)); + } + + #[test] + fn terminal_beats_a_live_row_from_another_actor() { + // Otherwise one actor's stale Progress keeps a finished ticket alive. + let v = fold( + &[ + row("agent", TicketState::Progress, 10, Some(500)), + row("reviewer", TicketState::Done, 20, None), + ], + None, + ); + assert_eq!(v.state, TicketState::Done); + assert_eq!( + v.deadline, None, + "a finished ticket has nothing to wait for" + ); + assert!(!v.is_overdue(9_999)); + } + + #[test] + fn escalated_is_not_terminal() { + // Raising something to a human is not resolving it. An escalation that + // is then ignored is the exact case this system exists to catch. + let v = fold( + &[row("watchdog", TicketState::Escalated, 5, Some(50))], + None, + ); + assert!(!v.state.is_terminal()); + assert_eq!(v.deadline, Some(50)); + assert!(v.is_overdue(60)); + } + + #[test] + fn deadline_is_the_earliest_live_one_not_the_latest() { + // Taking the latest would let a long-running actor mask another's stall. + let v = fold( + &[ + row("slow", TicketState::Progress, 10, Some(9_000)), + row("stalled", TicketState::Acknowledged, 10, Some(60)), + ], + None, + ); + assert_eq!(v.deadline, Some(60)); + assert!(v.is_overdue(60), "the earlier deadline must fire"); + } + + #[test] + fn only_the_latest_row_per_actor_counts() { + // Out-of-order delivery must not resurrect a superseded state. + let v = fold( + &[ + row("agent", TicketState::Progress, 30, Some(900)), + row("agent", TicketState::Acknowledged, 10, Some(40)), + ], + None, + ); + assert_eq!(v.state, TicketState::Progress); + assert_eq!( + v.deadline, + Some(900), + "the superseded row's deadline must not linger" + ); + } + + #[test] + fn furthest_advanced_live_state_wins() { + let v = fold( + &[ + row("a", TicketState::Acknowledged, 10, Some(100)), + row("b", TicketState::Progress, 10, Some(200)), + ], + None, + ); + assert_eq!(v.state, TicketState::Progress); + } + + #[test] + fn failure_reason_survives_the_fold() { + // A failure a person cannot read is a failure they cannot act on. + let mut r = row("agent", TicketState::Failed, 10, None); + r.reason = Some("could not reach the repository".to_string()); + let v = fold(&[r], None); + assert_eq!(v.state, TicketState::Failed); + assert_eq!(v.reason.as_deref(), Some("could not reach the repository")); + } + + #[test] + fn unknown_state_tags_are_rejected_not_guessed() { + // A ticket in an unrecognised state must not silently look healthy. + assert_eq!( + TicketState::from_tag("ack"), + Some(TicketState::Acknowledged) + ); + assert_eq!(TicketState::from_tag("finished"), None); + assert_eq!(TicketState::from_tag(""), None); + } + + #[test] + fn tag_round_trips() { + for s in [ + TicketState::Open, + TicketState::Acknowledged, + TicketState::Progress, + TicketState::Escalated, + TicketState::Done, + TicketState::Failed, + ] { + assert_eq!(TicketState::from_tag(s.as_tag()), Some(s)); + } + } +} From 3eb9be1b61c1aaa3906c0cbd3ba95e7ef0394e1f Mon Sep 17 00:00:00 2001 From: QuicksilverSlick Date: Mon, 31 Aug 2026 09:09:11 -0500 Subject: [PATCH 07/10] docs(buzz-core): runnable walkthrough of the ticket fold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fold's rules are judgement calls, not implementation details — one person finishing a ticket while another is mid-work SHOULD arguably end it, but that is a decision someone has to agree with, and it is cheaper to disagree now than after the relay and watchdog depend on the semantics. Eight scenarios, printed with what the fold decides and whether the watchdog would act: cargo run -p buzz-core --example ticket_scenarios Covers the cases that actually happen: nobody picked it up, healthy progress, went quiet mid-work, one worker stalled behind a slow one, finished while another was still working, gave up with a reason, escalated then ignored, and out-of-order delivery. Written so the behaviour can be reviewed without reading Rust. Co-Authored-By: Claude Opus 5 --- crates/buzz-core/examples/ticket_scenarios.rs | 219 ++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 crates/buzz-core/examples/ticket_scenarios.rs diff --git a/crates/buzz-core/examples/ticket_scenarios.rs b/crates/buzz-core/examples/ticket_scenarios.rs new file mode 100644 index 00000000000..274f071a293 --- /dev/null +++ b/crates/buzz-core/examples/ticket_scenarios.rs @@ -0,0 +1,219 @@ +//! Walk the ticket fold through the situations that actually happen, and print +//! what it decides. +//! +//! Run with: +//! +//! ```text +//! cargo run -p buzz-core --example ticket_scenarios +//! ``` +//! +//! This exists so the fold's behaviour can be reviewed without reading Rust. +//! Every scenario below is a judgement call someone has to agree with — the +//! code cannot tell you whether "one person gives up while another is still +//! working" should close the ticket. That is a decision, and it should be made +//! deliberately rather than discovered in production. + +use buzz_core::ticket::{fold, StatusRow, TicketState}; + +/// Seconds, for readability in the scenarios below. +const MIN: i64 = 60; +const HOUR: i64 = 60 * MIN; + +fn row(actor: &str, state: TicketState, at: i64, deadline: Option) -> StatusRow { + StatusRow { + actor: actor.to_string(), + state, + created_at: at, + not_before: deadline, + reason: None, + } +} + +fn failed(actor: &str, at: i64, why: &str) -> StatusRow { + StatusRow { + actor: actor.to_string(), + state: TicketState::Failed, + created_at: at, + not_before: None, + reason: Some(why.to_string()), + } +} + +/// Print one scenario: what happened, what the fold decided, and whether the +/// watchdog would act on it right now. +fn show(title: &str, story: &str, rows: &[StatusRow], open_deadline: Option, now: i64) { + let view = fold(rows, open_deadline); + + println!("\n\x1b[1m{title}\x1b[0m"); + println!(" {story}"); + println!(" ── decides ──"); + println!(" state {}", view.state.as_tag()); + match view.deadline { + Some(d) if d > now => println!(" deadline in {}", human(d - now)), + Some(d) => println!( + " deadline \x1b[33m{} ago — OVERDUE\x1b[0m", + human(now - d) + ), + None => println!(" deadline none (nothing is waiting on it)"), + } + if let Some(by) = &view.decided_by { + println!(" decided by {by}"); + } + if let Some(reason) = &view.reason { + println!(" reason {reason}"); + } + println!( + " watchdog {}", + if view.is_overdue(now) { + "\x1b[33mwould escalate this now\x1b[0m" + } else if view.state.is_terminal() { + "ignores it — finished" + } else { + "waiting, not yet due" + } + ); +} + +fn human(secs: i64) -> String { + match secs { + s if s < MIN => format!("{s}s"), + s if s < HOUR => format!("{}m", s / MIN), + s => format!("{}h", s / HOUR), + } +} + +fn main() { + // A fixed "now" so output is stable and reviewable. + let now = 10 * HOUR; + println!("\x1b[1mTicket fold — what it decides, and why\x1b[0m"); + println!("Each scenario is a judgement call. Disagree with any of them and say so."); + + show( + "1. Nobody picked it up", + "Craig reported a bug 45 seconds ago. No agent has said anything.", + &[], + Some(now - 15), // ack deadline was 30s after the report, 15s ago + now, + ); + + show( + "2. Being worked on, healthy", + "An agent acknowledged it and is posting progress. Next update due in 10 minutes.", + &[row( + "reset-agent", + TicketState::Progress, + now - 5 * MIN, + Some(now + 10 * MIN), + )], + None, + now, + ); + + show( + "3. Went quiet mid-work", + "An agent said it was working, then stopped renewing. Its progress deadline passed 12 minutes ago.", + &[row( + "reset-agent", + TicketState::Progress, + now - 27 * MIN, + Some(now - 12 * MIN), + )], + None, + now, + ); + + show( + "4. Two workers, one is stalled", + "A slow job is fine for hours, but a second actor's check was due 2 minutes ago.\n \ + The fold takes the EARLIEST deadline so the stall is not masked.", + &[ + row( + "long-build", + TicketState::Progress, + now - HOUR, + Some(now + 3 * HOUR), + ), + row( + "reviewer", + TicketState::Acknowledged, + now - 5 * MIN, + Some(now - 2 * MIN), + ), + ], + None, + now, + ); + + show( + "5. Finished, while someone else was still mid-work", + "The reviewer marked it done. A stale Progress row from the agent still exists.\n \ + Terminal wins, so the ticket does not stay alive forever on a stale row.", + &[ + row( + "reset-agent", + TicketState::Progress, + now - 20 * MIN, + Some(now + MIN), + ), + row("reviewer", TicketState::Done, now - MIN, None), + ], + None, + now, + ); + + show( + "6. Gave up, with a reason", + "The agent could not do it and said why. The requester can read this.", + &[failed( + "reset-agent", + now - 3 * MIN, + "the repository rejected the push: branch is protected", + )], + None, + now, + ); + + show( + "7. Escalated and then ignored", + "The watchdog raised it to a human 6.5 hours ago and nobody answered.\n \ + Escalation is NOT terminal, so this stays overdue and escalates again.", + &[row( + "watchdog", + TicketState::Escalated, + now - 7 * HOUR, + Some(now - 30 * MIN), + )], + None, + now, + ); + + show( + "8. Out-of-order delivery", + "An older Acknowledged arrived after a newer Progress from the same actor.\n \ + Only the latest row per actor counts, so the stale one cannot resurrect an old deadline.", + &[ + row( + "reset-agent", + TicketState::Progress, + now - MIN, + Some(now + 15 * MIN), + ), + row( + "reset-agent", + TicketState::Acknowledged, + now - 20 * MIN, + Some(now - 10 * MIN), + ), + ], + None, + now, + ); + + println!("\n\x1b[1mThe judgement calls worth disagreeing with\x1b[0m"); + println!(" · Scenario 5: one actor finishing ends the ticket, even if another was mid-work."); + println!( + " · Scenario 4: the earliest deadline wins, so a slow job cannot hide a stalled one." + ); + println!(" · Scenario 7: escalating is not resolving — an ignored escalation keeps firing."); + println!(" · Scenario 1: a ticket nobody accepted is still overdue, so silence is caught.\n"); +} From df7e3ee3c75fc4fbeccaa64acc376d345e2107ac Mon Sep 17 00:00:00 2001 From: QuicksilverSlick Date: Mon, 31 Aug 2026 09:23:18 -0500 Subject: [PATCH 08/10] fix(buzz-core): close three ways the ticket fold could go quiet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An adversarial review compiled this module standalone and executed the cases, rather than reasoning about them. Three holes were real, all in the same direction: the fold could stop watching a ticket that nobody had finished. 1. A LIVE ROW WITH NO DEADLINE READ AS "NEVER DUE". One Acknowledged row with not_before: None gave deadline: None, and is_overdue returned false at every representable timestamp. Any actor could silence the sweeper permanently by acknowledging without a deadline — and it was strictly worse than posting nothing, because open_deadline is consulted only when there are no rows at all. So acknowledging a ticket made it LESS watched than ignoring it. The module's premise — every non-terminal state carries the instant it must be acted on by — was documented and enforced nowhere. is_overdue now treats a live ticket with no deadline as due. "Nobody said when to check this" is not "never check this". Sweeping early is noisy; sweeping never is the failure this exists to prevent. The fold also falls back to open_deadline for a live row that named none. 2. A FAILURE COULD CARRY NO REASON. fold returned state: Failed, reason: None. The requirement is failed-with-reason and the struct's own comment said required, but nothing checked. A failure a person cannot read is a silent failure wearing a loud label. Missing reasons now render as MISSING_REASON rather than an empty field the reader has to interpret. 3. SAME-SECOND ROWS FROM ONE ACTOR RESOLVED BY STATE RANK. Nostr timestamps are whole seconds, so two rows from one actor in the same second is ordinary. Rank ordering let a same-second Done swallow the actor's own newer live row and terminalise the ticket forever. Ties now resolve fail-safe: prefer the non-terminal row, then the tighter deadline. An extra escalation on a finished ticket is noise; a dropped request is not recoverable. Deliberately NOT ordered by state rank, which was the reviewer's suggested fix. The confirmation pass ran the counterexample: with Acknowledged@100/nb=1000 against Progress@100/nb=130, rank ordering picks the loose deadline and goes silent at now=200 — producing exactly the failure it claimed to prevent. Rank and deadline tightness are independent, so tightness decides. 7 regression tests, one per hole plus order-independence and a guard that finished tickets do not sweep forever. 16 ticket tests, buzz-core green at 278. fmt and clippy clean. Co-Authored-By: Claude Opus 5 --- crates/buzz-core/src/ticket.rs | 190 +++++++++++++++++++++++++++++++-- 1 file changed, 180 insertions(+), 10 deletions(-) diff --git a/crates/buzz-core/src/ticket.rs b/crates/buzz-core/src/ticket.rs index 71322a09e91..0fd4174b033 100644 --- a/crates/buzz-core/src/ticket.rs +++ b/crates/buzz-core/src/ticket.rs @@ -121,9 +121,60 @@ impl TicketView { } } - /// Whether `now` is past the deadline and the ticket still expects action. + /// Whether the ticket still expects action and that action is now due. + /// + /// A live ticket with **no** deadline counts as overdue. "Nobody said when + /// to check this" is not the same as "never check this", and reading an + /// absent deadline as never-due is how a single malformed row silences the + /// sweeper permanently. Sweeping it immediately is noisy; not sweeping it + /// is the failure this whole system exists to prevent. pub fn is_overdue(&self, now: i64) -> bool { - !self.state.is_terminal() && self.deadline.is_some_and(|d| now >= d) + if self.state.is_terminal() { + return false; + } + match self.deadline { + Some(d) => now >= d, + None => true, + } + } +} + +/// Stand-in when a `failed` row carries no reason. +pub const MISSING_REASON: &str = "no reason was recorded"; + +/// Whether `candidate` should replace `current` as an actor's latest word. +/// +/// Recency decides it. The interesting case is a tie, which is ordinary rather +/// than exotic: Nostr timestamps are whole seconds, so two rows from one actor +/// in the same second is a normal occurrence and `StatusRow` carries nothing +/// finer to order them by. +/// +/// On a tie the rule is "stay watched", applied in two steps: +/// +/// 1. **Prefer the non-terminal row.** An extra escalation on a finished +/// ticket is noise; a dropped request is not recoverable. +/// 2. **Then prefer the tighter deadline**, treating "no deadline" as the +/// loosest. Sweeping early is survivable; sweeping never is the failure. +/// +/// Deliberately NOT ordered by [`TicketState`] rank. Rank and deadline +/// tightness are independent: an `Acknowledged` row can carry a looser +/// deadline than a `Progress` row from the same second, so preferring the +/// lower rank would discard the tighter deadline and produce exactly the +/// silent ticket this is meant to prevent. +fn supersedes(candidate: &StatusRow, current: &StatusRow) -> bool { + if candidate.created_at != current.created_at { + return candidate.created_at > current.created_at; + } + match (candidate.state.is_terminal(), current.state.is_terminal()) { + (false, true) => return true, + (true, false) => return false, + _ => {} + } + // Tighter deadline wins; a row that names one beats a row that does not. + match (candidate.not_before, current.not_before) { + (Some(c), Some(k)) => c < k, + (Some(_), None) => true, + _ => false, } } @@ -156,7 +207,7 @@ pub fn fold(rows: &[StatusRow], open_deadline: Option) -> TicketView { latest .entry(r.actor.as_str()) .and_modify(|cur| { - if (r.created_at, r.state) > (cur.created_at, cur.state) { + if supersedes(r, cur) { *cur = r; } }) @@ -173,7 +224,13 @@ pub fn fold(rows: &[StatusRow], open_deadline: Option) -> TicketView { state: t.state, deadline: None, decided_by: Some(t.actor.clone()), - reason: t.reason.clone(), + // A failure a person cannot read is a silent failure wearing a + // loud label. If the reason is missing, say so rather than + // rendering an empty field the reader has to interpret. + reason: match (t.state, &t.reason) { + (TicketState::Failed, None) => Some(MISSING_REASON.to_string()), + _ => t.reason.clone(), + }, }; } @@ -182,12 +239,20 @@ pub fn fold(rows: &[StatusRow], open_deadline: Option) -> TicketView { .max_by_key(|r| (r.state, r.created_at)) .expect("non-empty"); - // Earliest live deadline: fire on the first thing that should have happened. - let deadline = latest - .values() - .filter(|r| !r.state.is_terminal()) - .filter_map(|r| r.not_before) - .min(); + // Earliest live deadline: fire on the first thing that should have + // happened. A live row that named no deadline falls back to + // `open_deadline` rather than contributing nothing — otherwise an actor + // who acknowledges without a deadline makes the ticket LESS watched than + // if they had said nothing at all. + let live = latest.values().filter(|r| !r.state.is_terminal()); + let mut deadline: Option = None; + for r in live { + let d = r.not_before.or(open_deadline); + deadline = match (deadline, d) { + (Some(a), Some(b)) => Some(a.min(b)), + (a, b) => a.or(b), + }; + } TicketView { state: decided.state, @@ -318,6 +383,111 @@ mod tests { assert_eq!(TicketState::from_tag(""), None); } + // ── Regressions from the adversarial review ─────────────────────── + // + // Each of these reproduced a real hole found by independently compiling + // and running this module. They are the cases where the fold used to go + // quiet, which is the one thing a watchdog must never do. + + #[test] + fn a_live_row_with_no_deadline_is_swept_not_ignored() { + // Was: an Acknowledged row with no deadline gave deadline=None, and + // is_overdue returned false at EVERY representable timestamp — so one + // lazy row silenced the sweeper forever. Worse than posting nothing, + // since a ticket with no rows at least keeps its ack deadline. + let v = fold(&[row("agent", TicketState::Acknowledged, 10, None)], None); + assert!( + v.is_overdue(i64::MAX / 2), + "a live ticket nobody gave a deadline must be checked, not ignored" + ); + assert!( + v.is_overdue(0), + "unknown deadline means check now, not never" + ); + } + + #[test] + fn a_live_row_with_no_deadline_falls_back_to_the_ack_deadline() { + // Acknowledging must never make a ticket LESS watched than silence. + let with_row = fold( + &[row("agent", TicketState::Acknowledged, 10, None)], + Some(100), + ); + let without_row = fold(&[], Some(100)); + assert_eq!( + with_row.deadline, without_row.deadline, + "an ack that names no deadline must not discard the ack deadline" + ); + assert!(with_row.is_overdue(100)); + } + + #[test] + fn a_terminal_ticket_with_no_deadline_is_still_not_overdue() { + // The fix above must not make finished tickets sweep forever. + for st in [TicketState::Done, TicketState::Failed] { + let v = fold(&[row("agent", st, 10, None)], Some(1)); + assert!(!v.is_overdue(i64::MAX / 2), "{st:?} is finished"); + } + } + + #[test] + fn a_failure_without_a_reason_still_says_something() { + // A failure a person cannot read is a silent failure wearing a loud + // label. Nothing enforced the reason, so an empty field reached the + // reader. + let v = fold(&[row("agent", TicketState::Failed, 10, None)], None); + assert_eq!(v.state, TicketState::Failed); + assert_eq!(v.reason.as_deref(), Some(MISSING_REASON)); + } + + #[test] + fn same_second_ties_keep_the_ticket_watched_not_terminal() { + // Was: a same-second Done swallowed the actor's own newer live row + // via state-rank ordering, terminalising the ticket permanently. + // Nostr timestamps are whole seconds, so this is ordinary. + let v = fold( + &[ + row("agent", TicketState::Done, 100, None), + row("agent", TicketState::Acknowledged, 100, Some(130)), + ], + None, + ); + assert_eq!( + v.state, + TicketState::Acknowledged, + "on a tie, prefer the row that keeps the ticket watched" + ); + assert!(v.is_overdue(200)); + } + + #[test] + fn same_second_ties_keep_the_tighter_deadline() { + // The counterexample that disproved the reviewer's proposed fix: + // ordering by state RANK would pick Acknowledged (loose, 1000) over + // Progress (tight, 130) and go silent at now=200. Rank and deadline + // tightness are independent, so tightness is what must decide. + let v = fold( + &[ + row("agent", TicketState::Acknowledged, 100, Some(1000)), + row("agent", TicketState::Progress, 100, Some(130)), + ], + None, + ); + assert_eq!(v.deadline, Some(130), "the tighter deadline must survive"); + assert!( + v.is_overdue(200), + "must still fire — this is the silent-ticket case" + ); + } + + #[test] + fn same_second_tie_break_is_order_independent() { + // Whichever order the relay hands them to us, the answer must match. + let a = row("agent", TicketState::Done, 100, None); + let b = row("agent", TicketState::Acknowledged, 100, Some(130)); + assert_eq!(fold(&[a.clone(), b.clone()], None), fold(&[b, a], None)); + } + #[test] fn tag_round_trips() { for s in [ From 4d8938aaed579293083b417cc2b992235f5f2d29 Mon Sep 17 00:00:00 2001 From: QuicksilverSlick Date: Mon, 31 Aug 2026 09:35:11 -0500 Subject: [PATCH 09/10] fix(buzz-core): completion beats a same-instant timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The watchdog's clock and a worker's completion can land in the same second. The terminal row was chosen with max_by_key((created_at, state)), and Failed ranks above Done, so the watchdog's timeout won — the requester was told their request failed, with a reason that had not actually happened, while the work had in fact completed. A false failure is worse than a late success. Owner's call, and the right one: completion wins. Ties now select the lower-ranked terminal state via Reverse, and the test asserts it in both arrival orders, since the fold must not depend on which row the relay hands over first. Co-Authored-By: Claude Opus 5 --- crates/buzz-core/src/ticket.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/crates/buzz-core/src/ticket.rs b/crates/buzz-core/src/ticket.rs index 0fd4174b033..46b87f2d758 100644 --- a/crates/buzz-core/src/ticket.rs +++ b/crates/buzz-core/src/ticket.rs @@ -214,10 +214,16 @@ pub fn fold(rows: &[StatusRow], open_deadline: Option) -> TicketView { .or_insert(r); } + // Latest terminal row wins; on a same-instant tie, COMPLETION beats + // failure. If work finished in the very second the watchdog declared it + // timed out, the work actually happened — and telling someone their + // request failed, with a reason that is not true, is worse than a + // slightly late "done". `Reverse` picks the lower-ranked state on a tie, + // and Done ranks below Failed. let terminal = latest .values() .filter(|r| r.state.is_terminal()) - .max_by_key(|r| (r.created_at, r.state)); + .max_by_key(|r| (r.created_at, std::cmp::Reverse(r.state))); if let Some(t) = terminal { return TicketView { @@ -488,6 +494,26 @@ mod tests { assert_eq!(fold(&[a.clone(), b.clone()], None), fold(&[b, a], None)); } + #[test] + fn completion_beats_a_timeout_declared_in_the_same_instant() { + // The watchdog's clock and the worker's completion can land in the + // same second. Telling someone their request failed — with a timeout + // reason that did not actually happen — is worse than a late "done". + let done = row("agent", TicketState::Done, 100, None); + let mut timed_out = row("watchdog", TicketState::Failed, 100, None); + timed_out.reason = Some("acknowledgement deadline passed".to_string()); + + for rows in [vec![done.clone(), timed_out.clone()], vec![timed_out, done]] { + let v = fold(&rows, None); + assert_eq!( + v.state, + TicketState::Done, + "completion must win a same-instant tie, in either arrival order" + ); + assert_eq!(v.reason, None, "no false failure reason should survive"); + } + } + #[test] fn tag_round_trips() { for s in [ From eef026e2374de5cd6cf00eec9fb5c925f6476dd3 Mon Sep 17 00:00:00 2001 From: QuicksilverSlick Date: Mon, 31 Aug 2026 09:54:42 -0500 Subject: [PATCH 10/10] refactor(buzz-core)!: an actor's exit is not the ticket's outcome MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Research into how mature systems handle multi-actor work items turned up one near-universal rule that the previous model broke: no single participant's exit may write the outcome of shared work. Temporal states it flatly — "an Activity Failure will never directly cause a Workflow Failure". PagerDuty records a responder as joined or declined on the RESPONDER. ITIL has the requester confirm closure, never the implementer, because the party who did the work is the least able to judge whether the need was met and the most motivated to call it finished. The old fold had one enum and rule 1 was "terminal wins — if ANY actor says done or failed, the ticket is finished". So a Claude Code session that could not push a branch closed Craig's bug report for everyone, permanently, while a reviewer was still working it. The example program flagged that as a judgement call; it was a bug. Split into two axes that cannot be confused: Participation acknowledged / working / blocked / delivered / abandoned what ONE actor says about ITSELF; never terminal Outcome done(confirmation) / archived how the TICKET ended; only an Authority may write it Consequences, each with a test: - A worker giving up records Abandoned with its reason against itself. The ticket stays live, the reason is kept, and the actor drops out of the live set. This is the owner's instinct — archived with the reason so it stays tracked — honored at the right altitude. - Everyone leaving yields Unowned: not terminal, still swept, needing reassignment. That state previously had no name and folded to a deadline-less limbo. - Delivered is LIVE, not terminal. Finishing is not the same as being confirmed, and an unconfirmed delivery must not sit unnoticed. - An Outcome from an actor without authority is DEMOTED to the matching participation value and its author named in unauthorized_close. Obeying it is the bug; dropping it silently is the other bug. - Done carries a Confirmation, so a close because a person agreed and a close because nobody objected are different facts. The second names who delivered and how long the window was, and can never render as the first. - A close is deliberately not blocked by live work, so anyone interrupted is named rather than silently orphaned. Blocked (was Escalated) stays available to every actor: refusing an agent the ability to say it is stuck would itself be a silent failure. Which rung of the escalation ladder to use remains the watchdog's decision, so no actor can page a human harder by asking. Deliberately NOT adopted from the research: Kubernetes finalizers and Jira sub-task blocking conditions, both write-time enforcement with no implementation on this substrate, and the finalizer pattern imports a deadlock whose only documented remedy is an admin override that does not exist here. Recorded as a known gap rather than papered over: kind:30624 is addressable, so an actor can replace its own row and erase the Abandoned reason that triggered an escalation. Closing that needs status rows to become head pointers over a chain of regular transition events — a change to the event model, not to this fold. 19 tests. Nothing outside buzz-core consumes this yet, so the break is free to take now and would not be later. Co-Authored-By: Claude Opus 5 --- crates/buzz-core/examples/ticket_scenarios.rs | 240 +++-- crates/buzz-core/src/ticket.rs | 860 ++++++++++++------ 2 files changed, 730 insertions(+), 370 deletions(-) diff --git a/crates/buzz-core/examples/ticket_scenarios.rs b/crates/buzz-core/examples/ticket_scenarios.rs index 274f071a293..71fbe72d3d4 100644 --- a/crates/buzz-core/examples/ticket_scenarios.rs +++ b/crates/buzz-core/examples/ticket_scenarios.rs @@ -1,73 +1,110 @@ //! Walk the ticket fold through the situations that actually happen, and print //! what it decides. //! -//! Run with: -//! //! ```text //! cargo run -p buzz-core --example ticket_scenarios //! ``` //! //! This exists so the fold's behaviour can be reviewed without reading Rust. -//! Every scenario below is a judgement call someone has to agree with — the -//! code cannot tell you whether "one person gives up while another is still -//! working" should close the ticket. That is a decision, and it should be made -//! deliberately rather than discovered in production. +//! Every scenario is a judgement call someone has to agree with. -use buzz_core::ticket::{fold, StatusRow, TicketState}; +use buzz_core::ticket::{ + fold, Authority, Confirmation, Outcome, Participation, RowKind, StatusRow, +}; -/// Seconds, for readability in the scenarios below. const MIN: i64 = 60; const HOUR: i64 = 60 * MIN; -fn row(actor: &str, state: TicketState, at: i64, deadline: Option) -> StatusRow { +const CRAIG: &str = "craig (asked for it)"; +const OWNER: &str = "reset-agent (owns the project)"; +const WORKER: &str = "claude-session"; + +fn authority() -> Authority { + Authority { + requester: CRAIG.to_string(), + owner: Some(OWNER.to_string()), + } +} + +fn part(actor: &str, p: Participation, at: i64, deadline: Option) -> StatusRow { StatusRow { actor: actor.to_string(), - state, + kind: RowKind::Participation(p), created_at: at, - not_before: deadline, + deadline, reason: None, } } -fn failed(actor: &str, at: i64, why: &str) -> StatusRow { +fn gave_up(actor: &str, at: i64, why: &str) -> StatusRow { StatusRow { actor: actor.to_string(), - state: TicketState::Failed, + kind: RowKind::Participation(Participation::Abandoned), created_at: at, - not_before: None, + deadline: None, reason: Some(why.to_string()), } } -/// Print one scenario: what happened, what the fold decided, and whether the -/// watchdog would act on it right now. +fn closed(actor: &str, o: Outcome, at: i64, why: Option<&str>) -> StatusRow { + StatusRow { + actor: actor.to_string(), + kind: RowKind::Outcome(o), + created_at: at, + deadline: None, + reason: why.map(str::to_string), + } +} + fn show(title: &str, story: &str, rows: &[StatusRow], open_deadline: Option, now: i64) { - let view = fold(rows, open_deadline); + let v = fold(rows, open_deadline, &authority()); println!("\n\x1b[1m{title}\x1b[0m"); println!(" {story}"); - println!(" ── decides ──"); - println!(" state {}", view.state.as_tag()); - match view.deadline { - Some(d) if d > now => println!(" deadline in {}", human(d - now)), + println!(" -- decides --"); + println!(" state {}", v.state.as_tag()); + match v.deadline { + Some(d) if d > now => println!(" deadline in {}", human(d - now)), Some(d) => println!( - " deadline \x1b[33m{} ago — OVERDUE\x1b[0m", + " deadline \x1b[33m{} ago - OVERDUE\x1b[0m", human(now - d) ), - None => println!(" deadline none (nothing is waiting on it)"), + None => println!(" deadline none (nothing is waiting on it)"), + } + if let Some(reason) = &v.reason { + println!(" reason {reason}"); } - if let Some(by) = &view.decided_by { - println!(" decided by {by}"); + if let Some(c) = &v.confirmation { + let how = match c { + Confirmation::ByRequester => "the person who asked confirmed it".to_string(), + Confirmation::ByOwner => "the project owner confirmed it".to_string(), + Confirmation::Unopposed { delivered_by, .. } => { + format!("nobody objected after {delivered_by} delivered") + } + }; + println!(" closed how {how}"); } - if let Some(reason) = &view.reason { - println!(" reason {reason}"); + for (who, why) in &v.abandoned { + println!(" gave up {who} - {why}"); + } + if !v.unauthorized_close.is_empty() { + println!( + " \x1b[33mrefused\x1b[0m {} tried to close it without authority", + v.unauthorized_close.join(", ") + ); + } + if !v.interrupted.is_empty() { + println!( + " interrupted {} was still working when it closed", + v.interrupted.join(", ") + ); } println!( - " watchdog {}", - if view.is_overdue(now) { + " watchdog {}", + if v.is_overdue(now) { "\x1b[33mwould escalate this now\x1b[0m" - } else if view.state.is_terminal() { - "ignores it — finished" + } else if v.state.is_terminal() { + "ignores it - finished" } else { "waiting, not yet due" } @@ -83,25 +120,24 @@ fn human(secs: i64) -> String { } fn main() { - // A fixed "now" so output is stable and reviewable. let now = 10 * HOUR; - println!("\x1b[1mTicket fold — what it decides, and why\x1b[0m"); + println!("\x1b[1mTicket fold - what it decides, and why\x1b[0m"); println!("Each scenario is a judgement call. Disagree with any of them and say so."); show( "1. Nobody picked it up", "Craig reported a bug 45 seconds ago. No agent has said anything.", &[], - Some(now - 15), // ack deadline was 30s after the report, 15s ago + Some(now - 15), now, ); show( "2. Being worked on, healthy", - "An agent acknowledged it and is posting progress. Next update due in 10 minutes.", - &[row( - "reset-agent", - TicketState::Progress, + "An agent is working and posting progress. Next update due in 10 minutes.", + &[part( + WORKER, + Participation::Working, now - 5 * MIN, Some(now + 10 * MIN), )], @@ -111,10 +147,10 @@ fn main() { show( "3. Went quiet mid-work", - "An agent said it was working, then stopped renewing. Its progress deadline passed 12 minutes ago.", - &[row( - "reset-agent", - TicketState::Progress, + "The agent said it was working, then stopped renewing.", + &[part( + WORKER, + Participation::Working, now - 27 * MIN, Some(now - 12 * MIN), )], @@ -123,21 +159,20 @@ fn main() { ); show( - "4. Two workers, one is stalled", - "A slow job is fine for hours, but a second actor's check was due 2 minutes ago.\n \ - The fold takes the EARLIEST deadline so the stall is not masked.", + "4. One worker gives up, another is still on it", + "THE RULE THAT CHANGED. One worker quitting no longer ends the ticket -\n \ + it records who gave up and why, and the ticket stays live for everyone else.", &[ - row( - "long-build", - TicketState::Progress, - now - HOUR, - Some(now + 3 * HOUR), + gave_up( + WORKER, + now - 20 * MIN, + "the branch is protected, I cannot push", ), - row( + part( "reviewer", - TicketState::Acknowledged, + Participation::Working, now - 5 * MIN, - Some(now - 2 * MIN), + Some(now + 20 * MIN), ), ], None, @@ -145,64 +180,76 @@ fn main() { ); show( - "5. Finished, while someone else was still mid-work", - "The reviewer marked it done. A stale Progress row from the agent still exists.\n \ - Terminal wins, so the ticket does not stay alive forever on a stale row.", + "5. Everyone gave up", + "Both workers left. Nobody closed it, so it belongs to no one - and is still watched.", &[ - row( - "reset-agent", - TicketState::Progress, - now - 20 * MIN, - Some(now + MIN), - ), - row("reviewer", TicketState::Done, now - MIN, None), + gave_up(WORKER, now - 40 * MIN, "outside what I can do"), + gave_up("reviewer", now - 30 * MIN, "no capacity this week"), ], - None, + Some(now - 5 * MIN), now, ); show( - "6. Gave up, with a reason", - "The agent could not do it and said why. The requester can read this.", - &[failed( - "reset-agent", - now - 3 * MIN, - "the repository rejected the push: branch is protected", + "6. A worker tries to close it", + "The agent marked the whole ticket done. It has no authority to, so the\n \ + claim is demoted to delivered-awaiting-confirmation and surfaced.", + &[closed( + WORKER, + Outcome::Done(Confirmation::ByRequester), + now - 10 * MIN, + None, + )], + Some(now + 2 * HOUR), + now, + ); + + show( + "7. Delivered, waiting on Craig", + "The work is finished and waiting to be confirmed. Still swept, so an\n \ + unconfirmed delivery cannot sit unnoticed forever.", + &[part( + WORKER, + Participation::Delivered, + now - 30 * MIN, + Some(now + 90 * MIN), )], None, now, ); show( - "7. Escalated and then ignored", - "The watchdog raised it to a human 6.5 hours ago and nobody answered.\n \ - Escalation is NOT terminal, so this stays overdue and escalates again.", - &[row( - "watchdog", - TicketState::Escalated, - now - 7 * HOUR, - Some(now - 30 * MIN), + "8. Closed because nobody objected", + "Craig never replied within the window, so it closed - but visibly as a\n \ + second-class close that names who delivered.", + &[closed( + CRAIG, + Outcome::Done(Confirmation::Unopposed { + delivered_by: WORKER.to_string(), + window_secs: 2 * HOUR, + }), + now - MIN, + None, )], None, now, ); show( - "8. Out-of-order delivery", - "An older Acknowledged arrived after a newer Progress from the same actor.\n \ - Only the latest row per actor counts, so the stale one cannot resurrect an old deadline.", + "9. Craig calls it off while work is in flight", + "A close is not blocked by live work - but whoever was interrupted is named.", &[ - row( - "reset-agent", - TicketState::Progress, - now - MIN, - Some(now + 15 * MIN), + part( + WORKER, + Participation::Working, + now - 10 * MIN, + Some(now + MIN), ), - row( - "reset-agent", - TicketState::Acknowledged, - now - 20 * MIN, - Some(now - 10 * MIN), + closed( + CRAIG, + Outcome::Archived, + now - MIN, + Some("we shipped it another way"), ), ], None, @@ -210,10 +257,9 @@ fn main() { ); println!("\n\x1b[1mThe judgement calls worth disagreeing with\x1b[0m"); - println!(" · Scenario 5: one actor finishing ends the ticket, even if another was mid-work."); - println!( - " · Scenario 4: the earliest deadline wins, so a slow job cannot hide a stalled one." - ); - println!(" · Scenario 7: escalating is not resolving — an ignored escalation keeps firing."); - println!(" · Scenario 1: a ticket nobody accepted is still overdue, so silence is caught.\n"); + println!(" - Only the person who asked, or the project owner, can end a ticket."); + println!(" - A worker giving up is recorded with its reason and never ends the ticket."); + println!(" - Finishing is not closing: delivered work waits to be confirmed."); + println!(" - A ticket everyone walked away from is unowned, not finished."); + println!(" - Nobody objecting closes it, but that never reads as a person agreeing.\n"); } diff --git a/crates/buzz-core/src/ticket.rs b/crates/buzz-core/src/ticket.rs index 46b87f2d758..5283bbdd683 100644 --- a/crates/buzz-core/src/ticket.rs +++ b/crates/buzz-core/src/ticket.rs @@ -5,14 +5,37 @@ //! stopped moving. This module is the part with no I/O: given a ticket's status //! events, decide the current state and when the next deadline expires. //! +//! # Two axes, deliberately not one +//! +//! An actor's statement about *itself* and the *ticket's* outcome are different +//! kinds of fact, and collapsing them is the bug this module was rebuilt to +//! remove. Every mature tracker separates them — Temporal states it flatly +//! ("an Activity Failure will never directly cause a Workflow Failure"), +//! PagerDuty records a responder as joined or declined on the *responder*, and +//! ITIL has the requester confirm closure rather than the implementer. +//! +//! So one worker giving up records [`Participation::Abandoned`] against +//! **itself**, with a reason, and the ticket stays live for everyone else. Only +//! an [`Authority`] — the requester or the owner — may write an [`Outcome`]. +//! //! # Why a fold rather than a stored status column //! -//! State is derived from [`crate::kind::KIND_TICKET_STATUS`] events, one live -//! row per (ticket, actor). Deriving it means the events stay canonical — there -//! is no second copy to drift, and a replayed log always reproduces the same -//! answer. It also means an actor can only ever speak for themselves: a status -//! row is addressed by `(ticket, actor)`, and NIP-33 binds the author pubkey -//! into the replacement key, so nobody can forge another actor's row. +//! State is derived from [`crate::kind::KIND_TICKET_STATUS`] events. Deriving +//! it means the events stay canonical, and an actor can only ever speak for +//! themselves: a status row is addressed by `(ticket, actor)`, and NIP-33 binds +//! the author pubkey into the replacement key, so nobody can forge another +//! actor's row. +//! +//! # Known gap: rows are replaceable, so an actor can rewrite its own leaf +//! +//! `kind:30624` is addressable, so only the latest event per +//! `(pubkey, kind, d)` is guaranteed to be stored. An actor that recorded +//! `Abandoned` with a reason on Monday can replace that row on Wednesday, and +//! the evidence that triggered an escalation is gone. Nothing here can prevent +//! that: it is a property of the storage class, not of this fold. Closing it +//! needs status rows to become head pointers over a chain of *regular* (non +//! replaceable) transition events, which is a change to the event model rather +//! than to this module. Recorded here so it is not mistaken for solved. //! //! # Why deadlines are part of the state //! @@ -23,46 +46,167 @@ use std::collections::BTreeMap; -/// What an actor last said about a ticket. +/// Stand-in when a row that owes a reason arrives without one. +pub const MISSING_REASON: &str = "no reason was recorded"; + +/// What one actor says about its **own** involvement. /// -/// Ordering is deliberate: [`Ord`] ranks terminal states above live ones so a -/// fold can take the max without special-casing. +/// None of these end the ticket. An actor may write any of them about itself +/// at any time, which is deliberate: refusing an agent the ability to say it is +/// stuck would itself be a silent failure. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Participation { + /// Accepted it. Proves the request was not lost in transit. + Acknowledged, + /// Doing it, renewed periodically. A stale `Working` means something hung. + Working, + /// Stuck and saying so. In-project only — this wakes nobody's phone. Which + /// rung of the escalation ladder to use is the watchdog's decision alone, + /// so no actor can page a human harder by asking. + Blocked, + /// Finished its part, awaiting confirmation. Deliberately **live**, not + /// terminal: an unconfirmed delivery that nobody looks at must not sit + /// unnoticed forever. + Delivered, + /// Gave up, with a reason. Removes this actor from the live set; it does + /// **not** end the ticket. + Abandoned, +} + +/// Why a ticket was closed as done. +/// +/// A close because a person said so and a close because nobody objected are +/// different facts, and the second must never render as the first. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Confirmation { + /// The requester confirmed they got what they asked for. + ByRequester, + /// The project owner confirmed on their behalf. + ByOwner, + /// Nobody objected before the confirmation window closed. Visibly a + /// second-class close: it names who delivered and how long the window was. + Unopposed { + /// Actor whose delivery went unopposed. + delivered_by: String, + /// How long the window was, in seconds. + window_secs: i64, + }, +} + +/// How the **ticket** ended. Only an [`Authority`] may write one. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Outcome { + /// The request was met. + Done(Confirmation), + /// Deliberately stopped, with a reason. Distinct from done, and still + /// reopenable — this is "we are not doing this", not "this never happened". + Archived, +} + +/// What a row asserts: something about its author, or something about the +/// ticket. +/// +/// A union rather than one flat enum so a fold rule cannot accidentally treat +/// an actor's exit as the ticket's outcome. That confusion was the bug. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum RowKind { + /// A statement about the author's own involvement. + Participation(Participation), + /// A statement about how the ticket ended. + Outcome(Outcome), +} + +/// Who is allowed to close a ticket. +/// +/// The fold cannot answer "may this actor end this?" without being told, and +/// every authority rule depends on it. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct Authority { + /// Hex pubkey of whoever made the request. Always allowed to close. + pub requester: String, + /// Hex pubkey of the owning project agent, if any. + pub owner: Option, +} + +impl Authority { + /// Whether `actor` may write an [`Outcome`] that the fold will honour. + pub fn may_close(&self, actor: &str) -> bool { + actor == self.requester || self.owner.as_deref() == Some(actor) + } +} + +/// One actor's latest word on a ticket. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StatusRow { + /// Hex pubkey of whoever published this row. + pub actor: String, + /// What this row asserts. + pub kind: RowKind, + /// Unix seconds the row was created. + pub created_at: i64, + /// Unix seconds by which this must change, while the row is live. + pub deadline: Option, + /// Why — required in spirit for `Abandoned` and `Archived`. Rows arriving + /// off the wire without one fold to [`MISSING_REASON`] rather than an + /// empty field the reader has to interpret. + pub reason: Option, +} + +impl StatusRow { + /// Whether this row keeps its author in the live set. + fn is_live(&self) -> bool { + matches!( + self.kind, + RowKind::Participation( + Participation::Acknowledged + | Participation::Working + | Participation::Blocked + | Participation::Delivered + ) + ) + } +} + +/// The state a person sees. Ordered so that on a same-instant tie, completion +/// outranks abandonment — see [`fold`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum TicketState { /// Written down, nobody has picked it up. Open, - /// Someone accepted it. Proves the request was not lost in transit. - Acknowledged, - /// Work is happening, renewed periodically. A stale Progress is the signal - /// that something hung — silence, not an error. - Progress, - /// Raised to a human because a deadline passed or the agent gave up. - Escalated, - /// Terminal: finished. + /// Somebody is on it. + Working, + /// Somebody is stuck and said so. + Blocked, + /// Work is finished and waiting to be confirmed. + Delivered, + /// Everybody who was on it has left. Still live, and needs reassigning. + Unowned, + /// The request was met. Done, - /// Terminal: gave up, with a reason the requester can read. - Failed, + /// Deliberately stopped. + Archived, } impl TicketState { /// Whether no further action is expected. /// - /// `Escalated` is deliberately NOT terminal: raising something to a human - /// is not the same as resolving it. A ticket that escalates and is then - /// ignored is exactly the case this system exists to catch. + /// Only an [`Outcome`] is terminal. `Blocked` and `Unowned` are not: + /// raising a hand is not resolving, and a ticket everyone walked away from + /// is the case this system exists to catch. pub const fn is_terminal(self) -> bool { - matches!(self, TicketState::Done | TicketState::Failed) + matches!(self, TicketState::Done | TicketState::Archived) } /// Machine-readable form used in the `s` tag. pub const fn as_tag(self) -> &'static str { match self { TicketState::Open => "open", - TicketState::Acknowledged => "ack", - TicketState::Progress => "progress", - TicketState::Escalated => "escalated", + TicketState::Working => "working", + TicketState::Blocked => "blocked", + TicketState::Delivered => "delivered", + TicketState::Unowned => "unowned", TicketState::Done => "done", - TicketState::Failed => "failed", + TicketState::Archived => "archived", } } @@ -71,32 +215,17 @@ impl TicketState { pub fn from_tag(s: &str) -> Option { Some(match s { "open" => TicketState::Open, - "ack" => TicketState::Acknowledged, - "progress" => TicketState::Progress, - "escalated" => TicketState::Escalated, + "working" => TicketState::Working, + "blocked" => TicketState::Blocked, + "delivered" => TicketState::Delivered, + "unowned" => TicketState::Unowned, "done" => TicketState::Done, - "failed" => TicketState::Failed, + "archived" => TicketState::Archived, _ => return None, }) } } -/// One actor's latest word on a ticket. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct StatusRow { - /// Hex pubkey of whoever published this row. - pub actor: String, - /// What this actor last said about the ticket. - pub state: TicketState, - /// Unix seconds the row was created. - pub created_at: i64, - /// Unix seconds by which this state must change, if it is not terminal. - pub not_before: Option, - /// Required when `state` is [`TicketState::Failed`] — a failure a person - /// cannot read is a failure they cannot act on. - pub reason: Option, -} - /// The resolved state of one ticket. #[derive(Debug, Clone, PartialEq, Eq)] pub struct TicketView { @@ -106,8 +235,20 @@ pub struct TicketView { pub deadline: Option, /// Which actor's row decided `state`. pub decided_by: Option, - /// Why it failed, carried through from the deciding row. + /// Why it ended, carried through from the deciding row. pub reason: Option, + /// How a `Done` was reached, when it was. + pub confirmation: Option, + /// Actors who gave up, with their reasons. Kept visible so a ticket three + /// people walked away from does not look identical to a fresh one. + pub abandoned: Vec<(String, String)>, + /// Actors who tried to close the ticket without the authority to do so. + /// Their rows are demoted, never obeyed — but obeying is the bug and + /// dropping them silently is the other bug, so they surface here. + pub unauthorized_close: Vec, + /// Actors still working when the ticket was closed. A close is deliberately + /// not blocked by live work, so the orphaned work has to appear somewhere. + pub interrupted: Vec, } impl TicketView { @@ -118,16 +259,19 @@ impl TicketView { deadline, decided_by: None, reason: None, + confirmation: None, + abandoned: Vec::new(), + unauthorized_close: Vec::new(), + interrupted: Vec::new(), } } /// Whether the ticket still expects action and that action is now due. /// /// A live ticket with **no** deadline counts as overdue. "Nobody said when - /// to check this" is not the same as "never check this", and reading an - /// absent deadline as never-due is how a single malformed row silences the - /// sweeper permanently. Sweeping it immediately is noisy; not sweeping it - /// is the failure this whole system exists to prevent. + /// to check this" is not "never check this", and reading an absent deadline + /// as never-due is how a single malformed row silences the sweeper + /// permanently. Sweeping early is noisy; not sweeping is the failure. pub fn is_overdue(&self, now: i64) -> bool { if self.state.is_terminal() { return false; @@ -139,69 +283,64 @@ impl TicketView { } } -/// Stand-in when a `failed` row carries no reason. -pub const MISSING_REASON: &str = "no reason was recorded"; - /// Whether `candidate` should replace `current` as an actor's latest word. /// /// Recency decides it. The interesting case is a tie, which is ordinary rather /// than exotic: Nostr timestamps are whole seconds, so two rows from one actor -/// in the same second is a normal occurrence and `StatusRow` carries nothing -/// finer to order them by. +/// in the same second is normal and [`StatusRow`] carries nothing finer. /// -/// On a tie the rule is "stay watched", applied in two steps: -/// -/// 1. **Prefer the non-terminal row.** An extra escalation on a finished -/// ticket is noise; a dropped request is not recoverable. -/// 2. **Then prefer the tighter deadline**, treating "no deadline" as the -/// loosest. Sweeping early is survivable; sweeping never is the failure. -/// -/// Deliberately NOT ordered by [`TicketState`] rank. Rank and deadline -/// tightness are independent: an `Acknowledged` row can carry a looser -/// deadline than a `Progress` row from the same second, so preferring the -/// lower rank would discard the tighter deadline and produce exactly the -/// silent ticket this is meant to prevent. +/// On a tie the rule is "stay watched": prefer the live row, then the tighter +/// deadline. Deliberately NOT ordered by state rank — rank and deadline +/// tightness are independent, so preferring a rank would discard the tighter +/// deadline and produce exactly the silent ticket this is meant to prevent. fn supersedes(candidate: &StatusRow, current: &StatusRow) -> bool { if candidate.created_at != current.created_at { return candidate.created_at > current.created_at; } - match (candidate.state.is_terminal(), current.state.is_terminal()) { - (false, true) => return true, - (true, false) => return false, + match (candidate.is_live(), current.is_live()) { + (true, false) => return true, + (false, true) => return false, _ => {} } - // Tighter deadline wins; a row that names one beats a row that does not. - match (candidate.not_before, current.not_before) { + match (candidate.deadline, current.deadline) { (Some(c), Some(k)) => c < k, (Some(_), None) => true, _ => false, } } +fn reason_or_placeholder(r: &Option) -> String { + r.clone().unwrap_or_else(|| MISSING_REASON.to_string()) +} + /// Resolve a ticket's current state from every actor's status row. /// /// Rules, in order: /// -/// 1. **Terminal wins.** If any actor says done or failed, the ticket is -/// finished. Anyone working on it has finished or given up, and a stale -/// Progress row from another actor must not keep it alive forever. -/// 2. **Otherwise the furthest-advanced live state wins**, so one actor still -/// reporting Progress keeps the ticket live even if another only ever -/// acknowledged it. -/// 3. **Ties break on recency**, so the most recent word wins. -/// 4. **The deadline is the EARLIEST** among live rows, not the latest. A -/// watchdog must fire on the first thing that should have happened; taking -/// the latest would let one long-running actor mask another's stall. -/// -/// `open_deadline` applies when there are no rows at all — the acknowledgement -/// deadline, which is what catches a request that never reached anyone. -pub fn fold(rows: &[StatusRow], open_deadline: Option) -> TicketView { +/// 1. **Only an [`Authority`] can end it.** An [`Outcome`] row from anyone else +/// is *demoted* to the matching participation value (`Done` → `Delivered`, +/// `Archived` → `Abandoned`) and its author is named in +/// [`TicketView::unauthorized_close`]. Obeying it is the bug; dropping it +/// silently is the other bug. +/// 2. **Among authorised outcomes the latest wins**, and on a same-instant tie +/// **completion beats abandonment**. If work finished in the very second +/// someone gave up, the work happened — telling a person their request was +/// abandoned, with a reason that is not true, is worse than a late "done". +/// 3. **Otherwise the ticket is live.** Its state is the furthest-advanced live +/// participation; ties break on recency. +/// 4. **Every actor having abandoned means [`TicketState::Unowned`]** — still +/// live, still swept, and needing reassignment. +/// 5. **The deadline is the EARLIEST** among live rows. A watchdog must fire on +/// the first thing that should have happened; taking the latest would let +/// one long-running actor mask another's stall. A live row naming no +/// deadline falls back to `open_deadline`, so acknowledging can never make a +/// ticket less watched than silence. +pub fn fold(rows: &[StatusRow], open_deadline: Option, authority: &Authority) -> TicketView { if rows.is_empty() { return TicketView::open(open_deadline); } - // One row per actor: the latest they published. Rows arriving out of order - // must not resurrect an older state. + // One row per actor: the latest they published. let mut latest: BTreeMap<&str, &StatusRow> = BTreeMap::new(); for r in rows { latest @@ -214,57 +353,123 @@ pub fn fold(rows: &[StatusRow], open_deadline: Option) -> TicketView { .or_insert(r); } - // Latest terminal row wins; on a same-instant tie, COMPLETION beats - // failure. If work finished in the very second the watchdog declared it - // timed out, the work actually happened — and telling someone their - // request failed, with a reason that is not true, is worse than a - // slightly late "done". `Reverse` picks the lower-ranked state on a tie, - // and Done ranks below Failed. - let terminal = latest - .values() - .filter(|r| r.state.is_terminal()) - .max_by_key(|r| (r.created_at, std::cmp::Reverse(r.state))); + let mut unauthorized_close = Vec::new(); + let mut abandoned = Vec::new(); + let mut authorized_outcomes: Vec<&StatusRow> = Vec::new(); + let mut live: Vec<&StatusRow> = Vec::new(); + + for r in latest.values() { + match &r.kind { + RowKind::Outcome(_) if authority.may_close(&r.actor) => authorized_outcomes.push(r), + RowKind::Outcome(o) => { + // Demoted, not obeyed — and never silently. + unauthorized_close.push(r.actor.clone()); + match o { + Outcome::Done(_) => live.push(r), + Outcome::Archived => { + abandoned.push((r.actor.clone(), reason_or_placeholder(&r.reason))) + } + } + } + RowKind::Participation(Participation::Abandoned) => { + abandoned.push((r.actor.clone(), reason_or_placeholder(&r.reason))) + } + RowKind::Participation(_) => live.push(r), + } + } + + abandoned.sort(); + unauthorized_close.sort(); + + // Rule 2: latest authorised outcome, completion winning a same-instant tie. + let closing = authorized_outcomes + .iter() + .max_by_key(|r| { + let rank = match &r.kind { + RowKind::Outcome(Outcome::Done(_)) => 0u8, + _ => 1u8, + }; + (r.created_at, std::cmp::Reverse(rank)) + }) + .copied(); - if let Some(t) = terminal { + if let Some(c) = closing { + let (state, confirmation) = match &c.kind { + RowKind::Outcome(Outcome::Done(conf)) => (TicketState::Done, Some(conf.clone())), + _ => (TicketState::Archived, None), + }; + let interrupted: Vec = live.iter().map(|r| r.actor.clone()).collect(); return TicketView { - state: t.state, + state, deadline: None, - decided_by: Some(t.actor.clone()), - // A failure a person cannot read is a silent failure wearing a - // loud label. If the reason is missing, say so rather than - // rendering an empty field the reader has to interpret. - reason: match (t.state, &t.reason) { - (TicketState::Failed, None) => Some(MISSING_REASON.to_string()), - _ => t.reason.clone(), + decided_by: Some(c.actor.clone()), + reason: match state { + TicketState::Archived => Some(reason_or_placeholder(&c.reason)), + _ => c.reason.clone(), }, + confirmation, + abandoned, + unauthorized_close, + interrupted, }; } - let decided = latest - .values() - .max_by_key(|r| (r.state, r.created_at)) - .expect("non-empty"); - - // Earliest live deadline: fire on the first thing that should have - // happened. A live row that named no deadline falls back to - // `open_deadline` rather than contributing nothing — otherwise an actor - // who acknowledges without a deadline makes the ticket LESS watched than - // if they had said nothing at all. - let live = latest.values().filter(|r| !r.state.is_terminal()); + // Rule 5: earliest live deadline, with the open deadline as a floor. let mut deadline: Option = None; - for r in live { - let d = r.not_before.or(open_deadline); + for r in &live { + let d = r.deadline.or(open_deadline); deadline = match (deadline, d) { (Some(a), Some(b)) => Some(a.min(b)), (a, b) => a.or(b), }; } + // Rule 4: everyone left. + if live.is_empty() { + return TicketView { + state: TicketState::Unowned, + deadline: deadline.or(open_deadline), + decided_by: None, + reason: None, + confirmation: None, + abandoned, + unauthorized_close, + interrupted: Vec::new(), + }; + } + + // Rule 3: furthest-advanced live participation; ties on recency. + let decided = live + .iter() + .max_by_key(|r| { + let p = match &r.kind { + RowKind::Participation(p) => *p, + // A demoted unauthorised Done reads as Delivered. + RowKind::Outcome(_) => Participation::Delivered, + }; + (p, r.created_at) + }) + .expect("live is non-empty"); + + let state = match &decided.kind { + RowKind::Participation(Participation::Acknowledged | Participation::Working) => { + TicketState::Working + } + RowKind::Participation(Participation::Blocked) => TicketState::Blocked, + RowKind::Participation(Participation::Delivered) => TicketState::Delivered, + RowKind::Participation(Participation::Abandoned) => unreachable!("filtered into abandoned"), + RowKind::Outcome(_) => TicketState::Delivered, + }; + TicketView { - state: decided.state, + state, deadline, decided_by: Some(decided.actor.clone()), reason: decided.reason.clone(), + confirmation: None, + abandoned, + unauthorized_close, + interrupted: Vec::new(), } } @@ -272,259 +477,368 @@ pub fn fold(rows: &[StatusRow], open_deadline: Option) -> TicketView { mod tests { use super::*; - fn row(actor: &str, state: TicketState, at: i64, not_before: Option) -> StatusRow { + const REQUESTER: &str = "craig"; + const OWNER: &str = "reset-agent"; + + fn auth() -> Authority { + Authority { + requester: REQUESTER.to_string(), + owner: Some(OWNER.to_string()), + } + } + + fn part(actor: &str, p: Participation, at: i64, deadline: Option) -> StatusRow { StatusRow { actor: actor.to_string(), - state, + kind: RowKind::Participation(p), created_at: at, - not_before, + deadline, reason: None, } } - #[test] - fn no_rows_is_open_and_carries_the_ack_deadline() { - // The case that matters most: a request nobody ever picked up. It must - // still have a deadline, or the commonest failure is the invisible one. - let v = fold(&[], Some(100)); - assert_eq!(v.state, TicketState::Open); - assert_eq!(v.deadline, Some(100)); - assert!(v.is_overdue(100), "overdue exactly at the deadline"); - assert!(!v.is_overdue(99)); + fn gave_up(actor: &str, at: i64, why: &str) -> StatusRow { + StatusRow { + actor: actor.to_string(), + kind: RowKind::Participation(Participation::Abandoned), + created_at: at, + deadline: None, + reason: Some(why.to_string()), + } + } + + fn closed(actor: &str, o: Outcome, at: i64, why: Option<&str>) -> StatusRow { + StatusRow { + actor: actor.to_string(), + kind: RowKind::Outcome(o), + created_at: at, + deadline: None, + reason: why.map(str::to_string), + } } + // ── The rule this module was rebuilt for ────────────────────────────── + #[test] - fn terminal_beats_a_live_row_from_another_actor() { - // Otherwise one actor's stale Progress keeps a finished ticket alive. + fn one_worker_giving_up_does_not_end_the_ticket() { + // The bug: any actor writing a terminal state closed the ticket for + // everyone. No mature tracker allows that — a worker's exit is an + // input to a decision, never the decision. let v = fold( &[ - row("agent", TicketState::Progress, 10, Some(500)), - row("reviewer", TicketState::Done, 20, None), + gave_up("claude-session", 10, "could not reach the repository"), + part("reviewer", Participation::Working, 12, Some(500)), ], None, + &auth(), + ); + assert!( + !v.state.is_terminal(), + "one worker leaving must not close it" + ); + assert_eq!(v.state, TicketState::Working); + assert_eq!( + v.deadline, + Some(500), + "the remaining worker's clock still runs" ); - assert_eq!(v.state, TicketState::Done); assert_eq!( - v.deadline, None, - "a finished ticket has nothing to wait for" + v.abandoned, + vec![( + "claude-session".to_string(), + "could not reach the repository".to_string() + )], + "the giving-up is recorded permanently, with its reason" ); - assert!(!v.is_overdue(9_999)); } #[test] - fn escalated_is_not_terminal() { - // Raising something to a human is not resolving it. An escalation that - // is then ignored is the exact case this system exists to catch. + fn everyone_leaving_makes_it_unowned_and_still_watched() { + // The state that had no name: nobody is working and nobody closed it. let v = fold( - &[row("watchdog", TicketState::Escalated, 5, Some(50))], - None, + &[ + gave_up("a", 10, "out of scope for me"), + gave_up("b", 12, "no capacity"), + ], + Some(100), + &auth(), ); - assert!(!v.state.is_terminal()); - assert_eq!(v.deadline, Some(50)); - assert!(v.is_overdue(60)); + assert_eq!(v.state, TicketState::Unowned); + assert!( + !v.state.is_terminal(), + "a ticket everyone walked away from is not finished" + ); + assert!(v.is_overdue(100), "it must still be swept, and reassigned"); + assert_eq!(v.abandoned.len(), 2); } #[test] - fn deadline_is_the_earliest_live_one_not_the_latest() { - // Taking the latest would let a long-running actor mask another's stall. + fn an_unauthorized_close_is_demoted_and_named() { + // Obeying it is the bug. Dropping it silently is the other bug. let v = fold( - &[ - row("slow", TicketState::Progress, 10, Some(9_000)), - row("stalled", TicketState::Acknowledged, 10, Some(60)), - ], - None, + &[closed( + "claude-session", + Outcome::Done(Confirmation::ByRequester), + 10, + None, + )], + Some(100), + &auth(), ); - assert_eq!(v.deadline, Some(60)); - assert!(v.is_overdue(60), "the earlier deadline must fire"); + assert!( + !v.state.is_terminal(), + "a worker cannot close on the requester's behalf" + ); + assert_eq!( + v.state, + TicketState::Delivered, + "demoted to awaiting confirmation" + ); + assert_eq!(v.unauthorized_close, vec!["claude-session".to_string()]); } #[test] - fn only_the_latest_row_per_actor_counts() { - // Out-of-order delivery must not resurrect a superseded state. + fn the_requester_can_close_and_the_owner_can_too() { + for who in [REQUESTER, OWNER] { + let v = fold( + &[closed( + who, + Outcome::Done(Confirmation::ByRequester), + 10, + None, + )], + None, + &auth(), + ); + assert_eq!(v.state, TicketState::Done, "{who} may close"); + assert!(v.state.is_terminal()); + } + } + + #[test] + fn delivered_is_live_so_an_unconfirmed_delivery_is_still_swept() { + // Two-phase terminal: finishing is not the same as being confirmed. let v = fold( - &[ - row("agent", TicketState::Progress, 30, Some(900)), - row("agent", TicketState::Acknowledged, 10, Some(40)), - ], + &[part( + "claude-session", + Participation::Delivered, + 10, + Some(200), + )], None, + &auth(), ); - assert_eq!(v.state, TicketState::Progress); - assert_eq!( - v.deadline, - Some(900), - "the superseded row's deadline must not linger" + assert_eq!(v.state, TicketState::Delivered); + assert!(!v.state.is_terminal()); + assert!( + v.is_overdue(200), + "an unconfirmed delivery must not sit unnoticed" ); } #[test] - fn furthest_advanced_live_state_wins() { + fn an_unopposed_close_never_renders_as_a_human_confirmation() { let v = fold( - &[ - row("a", TicketState::Acknowledged, 10, Some(100)), - row("b", TicketState::Progress, 10, Some(200)), - ], + &[closed( + REQUESTER, + Outcome::Done(Confirmation::Unopposed { + delivered_by: "claude-session".into(), + window_secs: 7200, + }), + 10, + None, + )], None, + &auth(), ); - assert_eq!(v.state, TicketState::Progress); + assert_eq!(v.state, TicketState::Done); + match v.confirmation { + Some(Confirmation::Unopposed { window_secs, .. }) => assert_eq!(window_secs, 7200), + other => panic!("the kind of close must survive the fold: {other:?}"), + } } #[test] - fn failure_reason_survives_the_fold() { - // A failure a person cannot read is a failure they cannot act on. - let mut r = row("agent", TicketState::Failed, 10, None); - r.reason = Some("could not reach the repository".to_string()); - let v = fold(&[r], None); - assert_eq!(v.state, TicketState::Failed); - assert_eq!(v.reason.as_deref(), Some("could not reach the repository")); + fn closing_over_live_work_names_who_was_interrupted() { + // A close is deliberately not blocked by live work, so the orphaned + // work has to surface somewhere. + let v = fold( + &[ + part("claude-session", Participation::Working, 10, Some(500)), + closed(REQUESTER, Outcome::Archived, 20, Some("no longer needed")), + ], + None, + &auth(), + ); + assert_eq!(v.state, TicketState::Archived); + assert_eq!(v.interrupted, vec!["claude-session".to_string()]); + assert_eq!(v.reason.as_deref(), Some("no longer needed")); } #[test] - fn unknown_state_tags_are_rejected_not_guessed() { - // A ticket in an unrecognised state must not silently look healthy. - assert_eq!( - TicketState::from_tag("ack"), - Some(TicketState::Acknowledged) + fn archiving_without_a_reason_still_says_something() { + let v = fold( + &[closed(REQUESTER, Outcome::Archived, 10, None)], + None, + &auth(), ); - assert_eq!(TicketState::from_tag("finished"), None); - assert_eq!(TicketState::from_tag(""), None); + assert_eq!(v.reason.as_deref(), Some(MISSING_REASON)); } - // ── Regressions from the adversarial review ─────────────────────── - // - // Each of these reproduced a real hole found by independently compiling - // and running this module. They are the cases where the fold used to go - // quiet, which is the one thing a watchdog must never do. + // ── Deadline and sweeping guarantees ────────────────────────────────── + + #[test] + fn no_rows_is_open_and_carries_the_ack_deadline() { + let v = fold(&[], Some(100), &auth()); + assert_eq!(v.state, TicketState::Open); + assert!(v.is_overdue(100)); + assert!(!v.is_overdue(99)); + } #[test] fn a_live_row_with_no_deadline_is_swept_not_ignored() { - // Was: an Acknowledged row with no deadline gave deadline=None, and - // is_overdue returned false at EVERY representable timestamp — so one - // lazy row silenced the sweeper forever. Worse than posting nothing, - // since a ticket with no rows at least keeps its ack deadline. - let v = fold(&[row("agent", TicketState::Acknowledged, 10, None)], None); - assert!( - v.is_overdue(i64::MAX / 2), - "a live ticket nobody gave a deadline must be checked, not ignored" + let v = fold( + &[part("a", Participation::Acknowledged, 10, None)], + None, + &auth(), ); assert!( - v.is_overdue(0), + v.is_overdue(i64::MAX / 2), "unknown deadline means check now, not never" ); } #[test] - fn a_live_row_with_no_deadline_falls_back_to_the_ack_deadline() { - // Acknowledging must never make a ticket LESS watched than silence. + fn acknowledging_never_makes_a_ticket_less_watched_than_silence() { let with_row = fold( - &[row("agent", TicketState::Acknowledged, 10, None)], + &[part("a", Participation::Acknowledged, 10, None)], Some(100), + &auth(), ); - let without_row = fold(&[], Some(100)); - assert_eq!( - with_row.deadline, without_row.deadline, - "an ack that names no deadline must not discard the ack deadline" + assert_eq!(with_row.deadline, fold(&[], Some(100), &auth()).deadline); + } + + #[test] + fn deadline_is_the_earliest_live_one_not_the_latest() { + // A four-hour build must not mask a stalled reviewer. + let v = fold( + &[ + part("slow", Participation::Working, 10, Some(9_000)), + part("stalled", Participation::Acknowledged, 10, Some(60)), + ], + None, + &auth(), ); - assert!(with_row.is_overdue(100)); + assert_eq!(v.deadline, Some(60)); } #[test] - fn a_terminal_ticket_with_no_deadline_is_still_not_overdue() { - // The fix above must not make finished tickets sweep forever. - for st in [TicketState::Done, TicketState::Failed] { - let v = fold(&[row("agent", st, 10, None)], Some(1)); - assert!(!v.is_overdue(i64::MAX / 2), "{st:?} is finished"); + fn a_closed_ticket_is_never_overdue() { + for o in [Outcome::Done(Confirmation::ByRequester), Outcome::Archived] { + let v = fold(&[closed(REQUESTER, o, 10, Some("x"))], Some(1), &auth()); + assert!(!v.is_overdue(i64::MAX / 2)); } } #[test] - fn a_failure_without_a_reason_still_says_something() { - // A failure a person cannot read is a silent failure wearing a loud - // label. Nothing enforced the reason, so an empty field reached the - // reader. - let v = fold(&[row("agent", TicketState::Failed, 10, None)], None); - assert_eq!(v.state, TicketState::Failed); - assert_eq!(v.reason.as_deref(), Some(MISSING_REASON)); + fn blocked_is_not_terminal_and_keeps_its_deadline() { + // Raising a hand is not resolving. An ignored block keeps firing. + let v = fold( + &[part("a", Participation::Blocked, 5, Some(50))], + None, + &auth(), + ); + assert_eq!(v.state, TicketState::Blocked); + assert!(!v.state.is_terminal()); + assert!(v.is_overdue(60)); } + // ── Ordering ────────────────────────────────────────────────────────── + #[test] - fn same_second_ties_keep_the_ticket_watched_not_terminal() { - // Was: a same-second Done swallowed the actor's own newer live row - // via state-rank ordering, terminalising the ticket permanently. - // Nostr timestamps are whole seconds, so this is ordinary. + fn completion_beats_abandonment_in_the_same_instant() { + // If work finished in the very second someone gave up, the work + // happened. A false "we stopped" is worse than a late "done". + let done = closed( + REQUESTER, + Outcome::Done(Confirmation::ByRequester), + 100, + None, + ); + let arch = closed(OWNER, Outcome::Archived, 100, Some("timed out")); + for rows in [vec![done.clone(), arch.clone()], vec![arch, done]] { + let v = fold(&rows, None, &auth()); + assert_eq!( + v.state, + TicketState::Done, + "completion must win, in either arrival order" + ); + } + } + + #[test] + fn same_second_ties_keep_the_actor_watched() { let v = fold( &[ - row("agent", TicketState::Done, 100, None), - row("agent", TicketState::Acknowledged, 100, Some(130)), + gave_up("agent", 100, "giving up"), + part("agent", Participation::Acknowledged, 100, Some(130)), ], None, + &auth(), ); assert_eq!( v.state, - TicketState::Acknowledged, - "on a tie, prefer the row that keeps the ticket watched" + TicketState::Working, + "prefer the row that stays watched" ); assert!(v.is_overdue(200)); } #[test] fn same_second_ties_keep_the_tighter_deadline() { - // The counterexample that disproved the reviewer's proposed fix: - // ordering by state RANK would pick Acknowledged (loose, 1000) over - // Progress (tight, 130) and go silent at now=200. Rank and deadline - // tightness are independent, so tightness is what must decide. + // Ordering by rank would pick the loose deadline and go silent. let v = fold( &[ - row("agent", TicketState::Acknowledged, 100, Some(1000)), - row("agent", TicketState::Progress, 100, Some(130)), + part("agent", Participation::Acknowledged, 100, Some(1000)), + part("agent", Participation::Working, 100, Some(130)), ], None, + &auth(), ); - assert_eq!(v.deadline, Some(130), "the tighter deadline must survive"); - assert!( - v.is_overdue(200), - "must still fire — this is the silent-ticket case" - ); - } - - #[test] - fn same_second_tie_break_is_order_independent() { - // Whichever order the relay hands them to us, the answer must match. - let a = row("agent", TicketState::Done, 100, None); - let b = row("agent", TicketState::Acknowledged, 100, Some(130)); - assert_eq!(fold(&[a.clone(), b.clone()], None), fold(&[b, a], None)); + assert_eq!(v.deadline, Some(130)); + assert!(v.is_overdue(200)); } #[test] - fn completion_beats_a_timeout_declared_in_the_same_instant() { - // The watchdog's clock and the worker's completion can land in the - // same second. Telling someone their request failed — with a timeout - // reason that did not actually happen — is worse than a late "done". - let done = row("agent", TicketState::Done, 100, None); - let mut timed_out = row("watchdog", TicketState::Failed, 100, None); - timed_out.reason = Some("acknowledgement deadline passed".to_string()); - - for rows in [vec![done.clone(), timed_out.clone()], vec![timed_out, done]] { - let v = fold(&rows, None); - assert_eq!( - v.state, - TicketState::Done, - "completion must win a same-instant tie, in either arrival order" - ); - assert_eq!(v.reason, None, "no false failure reason should survive"); - } + fn only_the_latest_row_per_actor_counts() { + let v = fold( + &[ + part("agent", Participation::Working, 30, Some(900)), + part("agent", Participation::Acknowledged, 10, Some(40)), + ], + None, + &auth(), + ); + assert_eq!( + v.deadline, + Some(900), + "a superseded deadline must not linger" + ); } #[test] fn tag_round_trips() { for s in [ TicketState::Open, - TicketState::Acknowledged, - TicketState::Progress, - TicketState::Escalated, + TicketState::Working, + TicketState::Blocked, + TicketState::Delivered, + TicketState::Unowned, TicketState::Done, - TicketState::Failed, + TicketState::Archived, ] { assert_eq!(TicketState::from_tag(s.as_tag()), Some(s)); } + assert_eq!(TicketState::from_tag("finished"), None); } }