Commit Graph
2259 Commits
Author SHA1 Message Date
WesandRelease Automation 1fb4910300 chore(release): release Buzz Desktop version 0.5.10
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
desktop-v0.5.10
2026-08-11 17:56:13 -06:00
Will PflegerandGitHub f35930104b fix(desktop): remove 0.5.9+ perf regressions, speed up get_channels (#5599)
Desktop input latency regressed sharply for users on v0.5.9 and worsened
on latest main: multi-second stalls when clicking back into the app,
slow fresh boots, intermittent lockups, and scroll/mouse degradation.
Reverting to `119a84897` (pre-0.5.9) was confirmed to resolve it,
isolating the regression to that range. Profiling a live production
renderer plus a commit-level audit of the range found three independent,
additive causes — fixed here — plus a long-standing `get_channels` cost
that made every remaining refetch expensive, also addressed here.

## 1. Focus-return refetch storm (`refetchOnWindowFocus`)

#5490 wired TanStack's `focusManager` to app focus and flipped ~20 query
sites to `refetchOnWindowFocus: true`. A focus return after >60s away
fires them all within milliseconds — and a click into an unfocused
window *is* a focus return, so the burst runs before the click is
processed. That is the "click into the composer, wait 5 seconds"
symptom, and it also explains why mouse input feels worse than keyboard
(clicks arrive with focus transitions; typing happens while already
focused). A 5-second `sample` of a live production renderer caught a
single window activity-state transition consuming ~1.25s of main-thread
time, dominated by `JSON.parse` in the focus listener's microtask drain.

#5535 already established the fix pattern but applied it to only two
families (channels, home-feed). This PR extends the same 5-minute
`staleTime` discipline to the remaining families: pulse (×5), workflows
(×4), agents (×4), forum (×2), presence, user-status, custom-emoji,
channel-templates, and the persona catalog. Polling cadences and
push-invalidation paths are untouched — interval refetches and
`invalidateQueries` both bypass `staleTime`, so live-update behavior is
unchanged. Each gated family exports its focus-refetch policy as an
options object that the production hook spreads into `useQuery`, and a
`focusRefetchPolicy.test.mjs` drives a `QueryObserver` with that same
production object — locking the policy behaviorally (fresh focus return
→ 0 fetches; stale → refetch) and failing if a hook's
`staleTime`/`refetchOnWindowFocus` wiring drifts.

Four families deliberately keep tighter freshness, all surfaces where
the 5-minute gate would suppress the only refresh path and none of which
feed the app-wide storm: `repo-sync-status` keeps its fresh focus
refetch (its inline comment documents the "committed in a terminal,
switched back to the app" flow as intended); the workflow-runs list
stale-gates at 10s because a remotely-started run has no push
invalidation and its conditional 1s poll is off while the cache shows no
active runs; the workflow list queries (`useChannelWorkflowsQuery` and
the all-channels aggregate) stale-gate at 10s because they have no poll
and no relay subscription, and mutation-driven invalidation only covers
this renderer — remote workflow creates/edits/deletes surface only via
focus refetch; and the managed-agent log stale-gates at one poll tick
(30s) so returning to a live agent log refreshes immediately. Run
approvals keep the 5-minute gate under
`RUN_APPROVALS_FOCUS_STALE_TIME_MS` — their focused 10s poll already
covers freshness.

## 2. Synchronous localStorage sweep on the boot/focus path

#5453's stale-cache sweep synchronously `getItem` + `JSON.parse`s every
whitelisted localStorage entry on the main thread (multi-MB on seasoned
profiles), scheduled with a `requestIdleCallback` timeout of 1.5s that
guaranteed it landed mid-boot, and re-armed on every hidden→visible
transition — stacking it onto the exact moment the focus storm fires.
#5454's `trimSelfProfileCaches()` additionally scanned every
localStorage key on every `writeSelfProfileCache()` call (which fires
per relay self-profile delivery at boot).

Now: the first sweep waits `BOOT_SWEEP_FLOOR_MS` (30s) after startup,
the scan is time-sliced across idle callbacks, and the visibility
trigger is removed — boot-delayed plus hourly still covers the 14-day
TTL contract. The sliced sweep re-checks staleness immediately before
each removal (a key rewritten fresh mid-sweep survives), isolates
per-key storage errors so one bad entry can't strand the rest of the
snapshot, defers oversized values once rather than parsing them on a
zero-budget slice, guarantees forward progress on timeout-fired
callbacks, and cancels its scheduled slice when stopped. The profile
trim keeps a lazily-initialized memoized key count so the common
under-cap write is O(1); the full parse scan runs only when the count
exceeds a cap, resyncs if external deletions made it stale, and a failed
scan skips the trim instead of aborting the write. Sweep semantics
(rules, TTLs, eviction) are unchanged, and tests cover the scheduling,
slice-progress, error-isolation, defer-once, and trim short-circuit
behaviors.

## 3. The macOS window was never opaque

#5478's glass appearance is correctly opt-in at the CSS layer, but the
compositor cost was baked in deeper than its native `on_webview_ready`
transparency call: the main window is declared `"transparent": true` in
`tauri.conf.json` (added for the original glass work in #1671), which
makes tao call `NSWindow.setOpaque(false)` at creation and resolve every
later `set_background_color(None)` to `clearColor` — and no runtime
`setOpaque(true)` path exists through tauri, while wry's runtime
background setter can only force the WKWebView's `drawsBackground` off,
never back on. So "restore the platform default" was unreachable: every
launch, glass or not, ran with a non-opaque NSWindow, defeating
WindowServer's opaque-window compositing fast path and forcing full
window compositing every frame — compounded by the existing
`backdrop-blur` chrome overlapping the scrolling timeline. This matches
the compositor-shaped symptoms (scroll and pointer input degrading
first).

The window is now created opaque (`"transparent": false`) and the
NSWindow layer is never made transparent at runtime. Glass never needed
a transparent window: behind-window `NSVisualEffectView` vibrancy
renders inside opaque windows (this is how Finder and Notes draw vibrant
sidebars); it only requires a transparent WKWebView canvas, which the
`set_window_vibrancy` enable path already establishes at runtime
(`macos-private-api` compiles that in independent of the window flag).
Enabling glass installs the vibrancy layer and then makes only the
webview canvas see-through; disabling clears the vibrancy layer — the
canvas may stay non-drawing afterwards (wry's flag is one-way at
runtime), which is harmless because glass-off CSS paints fully opaque
above an always-opaque NSWindow. The boot-path first-frame backing
writes touch only the NSWindow backing color and are therefore inert to
glass state regardless of how they order against the `ThemeProvider`'s
vibrancy call on a persisted-glass-on cold boot. Glass-off users (the
default) get an end-to-end opaque window from boot for the first time.

## 4. `get_channels`: serial round-trips and a multi-MB payload on every
refetch

The stale gates in (1) cut refetch frequency; this cuts the cost of the
refetches that legitimately remain (boot, and focus returns after more
than 5 minutes away — previously still a multi-second stall).
`get_channels` made ~8 fully serial relay round-trips (~3.2–3.6s at
1,100+ channels), then shipped the full `ChannelInfo` list — including
every channel's member pubkeys — across IPC, where the renderer's
`JSON.parse` of the multi-MB payload froze the main thread (the ~1.25s
stall captured in the live sample).

- **Concurrent stages**: the membership chain, the open-channel
directory scan, and the hidden-DM snapshot run concurrently, as do the
member-count and last-message queries that follow. The critical path
drops from ~8 sequential round-trips to 2 phases. Filters, limits,
pagination, and merge semantics are unchanged.
- **Not-modified short-circuit**: the command now takes a
client-supplied content hash (FNV-1a 64 over the channel list,
canonicalized by id and excluding `last_message_at`) and omits the
channel list from the response when nothing else changed. Last-message
timestamps — which change on nearly every message anywhere — ship as a
small separate map that the client overlays onto its cached list with
reference preservation, so React Query's structural sharing also skips
downstream re-renders. On a typical refocus the renderer parses
kilobytes instead of megabytes. The hash is stored in the query cache
itself, tying its lifecycle to the data it describes so a community
switch can never leak a stale hash.

The E2E mock bridge speaks the new payload shape — including the
complete `last_messages` map the client treats as authoritative — and
hash canonicalization plus overlay reference-preservation are
unit-tested on both sides.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-08-11 19:47:19 -04:00
9203bf60ee perf(desktop): coalesce read state localStorage persistence (#5591)
Follow-on to #5453/#5454's localStorage work — found while investigating
app-slowness reports on a real profile.

## Problem

`ReadStateManager.persistLocalState()` serialized and rewrote **all
three** read-state localStorage blobs (`buzz.channel-read-state.v2`,
`.publishable.v1`, `.source-created-at.v1`) synchronously on every
context advance. On a real profile (1,643 contexts, ~450K chars across
the three blobs) this produced ~880KB of localStorage sqlite WAL growth
per 30 seconds at idle, with writes every ~5s — steady main-thread
serialization + sync IPC for no user-visible benefit. Observed WAL size
on the affected profile: 94–114MB.

## Fix

- Local persistence coalesced behind a **1s trailing-edge timer**: a
burst of N advances produces one `writeStoredReadState` (one write per
blob).
- Pending dirty state **flushes synchronously** on `pagehide`, hidden
`visibilitychange`, `destroy()`, and before each relay publish — disk is
current before any relay event goes out.
- Hydration still persists immediately. Publish debounce (5s), merge
logic, and blob formats unchanged (`DEBOUNCE_MS` renamed to
`PUBLISH_DEBOUNCE_MS` only).

## Accepted residual

A hard kill (SIGKILL/power loss — not webview teardown) inside the 1s
window loses ≤1s of local read-state advances; relay max-merge bounds
the effect to a message flickering back unread. On the record per
review.

## Validation

- `readStateManager.test.mjs`: fake-timer/mock-storage coverage —
exactly one 3-blob write per burst (zero before the timer fires),
hidden-flush cancels the timer and persists, hydrate persists
immediately, pre-publish flush. Suite 26/26.
- Push gate at the pushed commit: desktop check, typecheck, full desktop
unit suite 4,670/4,670.
- Independent adversarial FULL REVIEW: **APPROVE** at tree `371a02cf`
(commit metadata rewritten afterward for attribution; tree identical) —
all six `persistLocalState` call sites traced, lifecycle/leak checks
(StrictMode remount, pubkey change), no external readers of the blob
keys.

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Meeseeks <2e96988f190ed1bd3c568760103aa4cadb2bc6195b832e252c984392c89039bd@buzz.block.builderlab.xyz>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-11 16:41:35 -07:00
Taksh KothariandGitHub 16b7ae7ce6 fix(relay): stop panicking the ingest worker on reactions to project events (#5294)
A NIP-25 reaction whose target is a project root or project comment
(kind
1621 issue, 1618 PR, or a kind-1 comment on one) carries no h tag, so
channel_id is None on the reaction write path. The conformance-trace
emission asserted a channel was always present:

channel: channel_label(channel_id.expect("reaction path has channel")),

so the worker panicked at ingest.rs:2824. The row was inserted before
the
panic, so the client saw a failed request for a persisted event and
retried,
and the duplicate branch carried the same expect, head-of-line blocking
a
durable publish queue forever.

Mirror the message write's three-way split at the same seam:
(Some, true) -> WriteInsert, (Some, false) -> WriteDuplicate, (None, _)
-> WriteInsertGlobal. The conformance vocabulary already models
channel-less
writes; only the reaction path was missing it.

Closes #4936

Signed-off-by: Taksh <takshkothari09@gmail.com>
Signed-off-by: Ravneet Arora <rarora@squareup.com>
2026-08-11 23:03:18 +00:00
d9dc76c0aa fix(desktop): bound initial timeline retention (#5603)
## Summary

- let Virtua own the initial visible timeline range instead of passing
every loaded row to `keepMounted`
- populate the existing bounded retention window after the virtualizer
reports its first settled viewport
- cover a 10,000-row timeline to prevent an all-history initial mount
regression

## Why

`useTimelineRetention` initialized its retained-key set with every
loaded timeline key. Those indices were passed to Virtua's
`keepMounted`, effectively defeating virtualization during initial
channel positioning until `onScrollEnd` pruned the set.

On a large real channel this grew WebContent into multiple gigabytes and
blocked the renderer main thread for 20+ seconds while WebKit laid out
and painted the retained rows. Starting with no retained rows restores
Virtua's visible-range mount; the existing reader-neighborhood and
visual-tail retention is populated once the viewport is measured.

## Validation

- `node --import ./test-loader.mjs --experimental-strip-types --test
src/features/messages/ui/useTimelineRetention.test.mjs`
- pre-push hook at `8e86a189de7e9a8f2cb119396c8f912ed9dacd6e`:
branch-skew, desktop-check, desktop-typecheck, and all 4,671 desktop
tests passed
- manual ablation against PR #5599 on the affected profile: catastrophic
channel-switch stalls disappeared

## Authorship disclosure

Carl implemented and is posting this change on Wes's behalf.

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-11 15:59:36 -07:00
Taksh KothariandGitHub e8153f8f27 fix(relay): log event kind on the HTTP bridge /events line (#5291)
The HTTP bridge request log recorded route, status, and accepted but not
the event kind, so typing indicators (kind 7) and their deletions (kind
5)
were indistinguishable from real messages (kind 9). Every agent turn
produced
accepted:true lines whether or not a message was actually sent, which
twice
led debuggers to conclude a silent agent had published successfully.

Add kind to the Ok outcome and the tracing::info line so the publish
path is
self-describing without a database query.

Closes #4676

Signed-off-by: Taksh <takshkothari09@gmail.com>
2026-08-11 15:44:28 -07:00
397796c5f3 feat(tracing): add PostgreSQL tracing spans (#3678)
## Why
Expose PostgreSQL datastore latency within existing request traces so
slow logical database operations can be identified without recording
tenant data or query arguments.

## What
- Add client spans around logical PostgreSQL operations across the
database facade, search, audit, replica fencing, and command persistence
- Use a dedicated `buzz_datastore` target and `db.system.name =
"postgresql"` for filtering and backend classification
- Exclude health-check database calls and scrub raw identifiers and
errors from newly traced paths

## Risk Assessment
Medium — this instruments frequently used datastore paths and increases
trace volume when enabled, but does not change SQL execution or
datastore behavior. Existing OpenTelemetry filtering controls export.

## References
- Pre-push clippy and fast unit-test hooks passed

Generated with Amp

---------

Signed-off-by: David Grochowski <dgrochowski@squareup.com>
Co-authored-by: Amp <amp@ampcode.com>
2026-08-12 08:04:54 +10:00
cf03bd7c37 Improve desktop search scoping (#5306)
## What changed

- unify Cmd+K and channel Cmd+F around a removable channel or
conversation scope
- add conservative fuzzy matching for people and channels while
preserving exact-match ordering
- make scoped message search complete for one-character queries and
expose up to 40 scrollable results
- keep the pre-scope channel or DM action in the normal results flow so
it scrolls away with the list

## Validation

- desktop TypeScript typecheck
- desktop text-size and file-size guards
- focused fuzzy-search unit tests (24 passed)
- focused search Playwright coverage (7 passed), including channel and
DM copy, one-character results/no-results, 40-result scrolling, and the
non-sticky scope action
- desktop E2E build
- visual review of channel, scoped, expanded-results, and DM states

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz>
2026-08-11 11:11:57 -07:00
cd2aa5c12d Add glass appearance and cohesive settings (#5478)
## Summary

- add an opt-in native glass sidebar with opacity controls and live
theme previews
- refine sidebar spacing and Buzz-only active rows while preserving
production defaults
- unify settings section cards, subtitles, and agent runtime rows

## Validation

- repository format, lint, type, and file-size checks
- 4,538 desktop tests and 2,270 native desktop tests
- desktop and web production builds
- 1,261 mobile tests in the completed full gate
- focused Playwright appearance, sidebar, settings, pairing, and runtime
coverage

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Signed-off-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
2026-08-11 10:25:23 -07:00
b0795a10ea Add Send to channel for thread messages (#5305)
## Summary

- Share eligible self-authored or owned-agent thread messages into the
parent channel as new top-level messages.
- Link the shared message back to the exact root thread with a semantic
channel label and excerpt.
- Add a dedicated channel-arrow icon plus ownership and navigation
coverage.

## Validation

- Desktop lint, size, and text guards
- Desktop TypeScript build and all 4,543 unit tests
- Focused Playwright send-to-channel and thread-link navigation tests

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-11 10:18:54 -07:00
bba3e06386 Fix macOS attachment picker lifecycle and allow inert HTML downloads (#5569)
## Problem

Canceling the native macOS file chooser leaves the composer's temporary,
detached `<input type="file">` without a `change` event or an explicit
cleanup path. Opening Finder again immediately creates a second detached
input while WebKit may still be unwinding the first picker. The newly
selected files can therefore fail to reach the upload pipeline. Drag and
drop is unaffected because it bypasses this picker lifecycle.

This does **not** add an automatic retry mechanism. “Retry” means the
user's next attachment attempt after canceling or after a prior
selection.

## Fix

- give each composer hook one hidden, body-mounted file input for its
lifetime instead of creating a detached one per click
- reset and reconfigure that input before every open, replace its
handler rather than stacking handlers, and remove it cleanly on unmount
- preserve normal selection, cancel then reopen, selecting the same file
again, and multi-select behavior
- accept canonical `text/html` attachments while continuing to serve and
render them strictly as inert downloads
- keep XHTML, SVG, JavaScript, and executable MIME types blocked

The picker change fixes the ownership/lifecycle bug at its source; it
does not retry failed uploads, add delays, or mask errors.

## Testing

- mandatory pre-push gate: branch-skew, desktop typecheck/tests/check,
Rust tests, and desktop Tauri checks passed on
`ea5a97adf957803935b28d63d32f9f332cf65287`
- `cargo test -p buzz-media --lib` (110 passed)
- `pnpm --dir desktop typecheck`
- focused Biome check for the three picker files
- picker Playwright regression: cancel/no selection then reopen, select
the same file again, and multiple selection (run on the source commit
before integration)
- HTML live-relay response regression added as ignored E2E because it
requires the S3-backed relay harness

## Manual verification

Playwright models cancellation with Chromium's
`FileChooser.setFiles([])`; it cannot exercise the native macOS Finder
panel/WebKit presentation lifecycle. Before merge, manually verify in
the built macOS app:

1. select a PNG normally
2. cancel, then immediately reopen and select a PNG
3. select the same PNG on a subsequent attempt
4. multi-select two PNGs

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Princess Donut <68157ebd23b3897c1991015c3038658ea916200c67d3a54620b0754d1b92f6e0@buzz.block.builderlab.xyz>
Co-authored-by: Mongo <5c25403eab7271f9f94ddd4f2b270e8cac2c92e2c830c51877cca6ec974ffb3f@buzz.block.builderlab.xyz>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-11 09:51:42 -07:00
d3ec831e0c fix(desktop): preserve fresh channel timelines (#5577)
## Summary

- skip the channel subscription catch-up request when the authoritative
channel window was fetched successfully within the existing five-minute
freshness period
- keep the responsive deferred skeleton for populated channel switches
instead of briefly rendering empty-channel actions
- preserve a real empty-channel intro across the first appended message
only after React has committed that empty state

This is intentionally narrow. It does not claim to solve the separate
sidebar startup cost or general main-thread stalls found during the
investigation.

### Related issue

N/A — no matching open issue or PR found.

### Testing

- pre-push desktop gate on `f1be6beea90b9715e04e5fc65cc5cfbe8210e0d9`:
  - desktop tests: 4,621 passed
  - desktop check: passed
  - desktop typecheck: passed
  - branch-skew: passed
- focused cache/surface/lifecycle tests: 62 passed
- manual diagnostic trace after rollback:
  - 16/16 channel revisits skipped catch-up refresh
  - 0 revisit refresh starts
  - 0 populated-channel empty/intro flashes
  - cached switches retained the deferred skeleton-to-list path

No screenshot: the regression is a transient channel-switch state and
request behavior, covered by lifecycle tests and the diagnostic trace
rather than a stable visual diff.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-11 09:37:12 -07:00
4935724494 fix(desktop): suppress fresh focus-return refetches for channels and home-feed (#5535)
Fixes the 0.5.9 sluggishness Wes reported in app-slowness-mac (channel
list slow, content slow).

## Problem

#5490 (shipped in 0.5.9) flipped ~20 query sites to
`refetchOnWindowFocus: true` and wired TanStack focusManager to app
focus. Instrumented at that exact commit: regaining focus after >60s
away fires **7 query fetches within 2ms**, including `get_channels`,
which settles at **~3.6s** (production probe: median 3.2s at 1,133
channels — 8 serial round-trips, 1,133-filter last-message batch). In
0.5.8 this burst was zero by configuration. Net: a burst of fetch/parse
contention exactly when the user returns to the app.

Relay ruled out: v0.2.1 small reads are 2–4ms upstream; nothing in
v0.2.0..v0.2.1 degrades the query path. The O(N) `get_channels` design
is a pre-existing issue (June analysis) — this PR fixes the new stampede
that made it user-visible.

## Fix

Raise `staleTime` to 5 minutes on the two expensive focus-refetch
families — `channels` and `home-feed` — so a focus return inside that
window serves cache instead of refetching. `refetchOnWindowFocus: true`
only refetches stale queries, so genuinely old data still refreshes on
return.

Unchanged: focused polling cadence (60s channels / 30s home-feed;
interval refetches ignore staleTime), #5490 blur quiescence (no changes
to `useDocumentVisible.ts`/`queryClient.ts`), all push-style
invalidation paths (`invalidateQueries` bypasses staleTime), and
channels cold-start revalidate (`initialDataUpdatedAt: 0`).

## Validation

- New regression test
`desktop/src/features/home/focusRefetchPolicy.test.mjs` (4/4): fresh
focus return → 0 fetches; stale → 1; polling constants locked.
- Pre-push gate at the reviewed tree: desktop-check, desktop-typecheck,
full desktop-test **4588/4588**.
- Independent adversarial review (Beth): APPROVE at tree `4e2546ec` —
verified fresh-skip/stale-refetch against query-core 5.100.14 source,
polling-cadence via browser-simulated probe, side-effect sweep of all
invalidation paths clean. Sole CHANGE was commit trailers, fixed by
amend (tree unchanged).

## Known residual

Focus returns after >5min still fire the full burst including the
~3.2–3.6s `get_channels`. This cuts stampede frequency, not magnitude —
the O(N) `get_channels` relay path (RESEARCH/GET_CHANNELS_SLOWNESS.md)
is the follow-up that fixes magnitude.

Diagnosis: Summer (focus profiling) + Morty (relay probe); implemented
by Meeseeks; reviewed by Beth; integrated by Rick. Thread:
app-slowness-mac
e78fad29380d9a0974c9d673910450994a228781ddce133a8cedbd90504d95be.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Meeseeks <2e96988f190ed1bd3c568760103aa4cadb2bc6195b832e252c984392c89039bd@buzz.block.builderlab.xyz>
2026-08-11 09:07:39 -07:00
240cdd3ea1 chore: mesh upgrade, clean up legacy special case code, simplify model selection for mesh (#5289)
Shared compute now has exactly two model choices: MeshLLM's virtual
`mesh`
model, or a model you name. Buzz picks between them in one place, and
buzz-agent no longer knows meshes exist.

## What changed

- **MeshLLM v0.74.0 → v0.75.1.** v0.75.0 added
`degrade_to_single_model`, so a
  `model=mesh` request is answered by one served model when there is no
committee to form, instead of failing. v0.75.1 adds Mesh-LLM#1196, which
skips stale pre-0.75 runtime cache entries rather than aborting startup
on
them — without it, anyone who had run mesh on 0.73/0.74 could not start.
- **Deleted the client-side mesh catalog probe.** buzz-agent used to
poll
`/v1/models` (5s TTL, 30s cooldown, two-observation debounce) to decide
whether `mesh` was safe to send. MeshLLM now decides per request, so the
  polling, its hysteresis, and its 503 fallback are gone.
- **One mapping point.** `relay_mesh_wire_model()` turns the stored
value into
a wire name: `auto` becomes `mesh`, a named model passes through. The
spawn
env, the ACP harness, and the readiness probe all use it, so they cannot
disagree — previously `BUZZ_ACP_MODEL` and the probe both said `auto`, a
name
  the mesh does not advertise.
- **Removed the `nostr-relay-pool` advisory exception.** #5404 allowed
RUSTSEC-2026-0243 "after mesh-llm migrates to nostr-sdk >= 0.45".
v0.75.1
does, so the retired crate is gone from both lockfiles and the exception
  would only mask a future advisory for it.
- **Deleted `scripts/ensure-mesh-native-runtime.sh`** and its six
justfile call
sites. It built llama.cpp from source into the runtime cache; the app
already
  downloads the signed release runtime itself, and CI never called it.

## Why it is better

**−639 lines of Rust.** Availability is decided by the node that knows
the
answer, per request, instead of by a client cache that could be stale
for up to
30 seconds. A second worker joining now takes effect on the next request
rather
than after two confirming probes.

## Behaviour change

A 503 on an explicit `mesh` request takes the ordinary transport retry
under
the same model instead of failing over to a second one — there is no
second
model to fail over to now. MoA repairs partial committee results
internally
before it reaches that point.

## Validation

`crates/buzz-relay/examples/mesh_agent_e2e.rs` now sends `mesh` where it
previously sent `auto` or the physical model id, so no leg was covering
what
Buzz actually puts on the wire. 4/4 on gemma-4-E4B, gemma-4-26B-A4B, and
Qwen3-8B — including a real ACP tool call through `mesh` into
buzz-dev-mcp,
asserted by reading the written file back off disk.

Hand-tested in the desktop app on both gemma-4 sizes: picked Auto, agent
logged
`model_id=mesh`, replied in channel.

## Not covered

A committee that forms and then loses a worker returns 502, and that
needs two
workers to reproduce — not testable on one machine.

---------

Signed-off-by: Michael Neale <michael.neale@gmail.com>
Co-authored-by: Michael Neale <michael.neale@gmail.com>
2026-08-11 11:40:07 -04:00
83ca595ada fix(desktop): preserve theme when opening communities (#5266)
**Category:** fix
**User Impact:** Previously selected community themes are preserved when
opening a relay through onboarding, while first-time theme migration
still completes for communities with no saved theme.

**Problem:** During community initialization, desktop queried theme
history before establishing live delivery. If a replacement event
arrived while an empty history query was in flight, the client could
incorrectly treat the theme as absent and publish the default over the
user's saved selection.

**Solution:** Subscribe before fetching history, expose whether live
readiness reached EOSE, flush buffered live events before resolving
EOSE, and retain the newest delivered replacement through hydration.
Seed the inherited/default theme only when both live and history
snapshots reach EOSE with no valid or unreadable event; subscription
failures, CLOSED, readiness timeout, relay failure, and unreadable
events fail closed without publishing.

<details>
<summary>File changes</summary>

**desktop/src/shared/api/relayClientSession.ts / relayClientShared.ts /
relayClosedRecovery.ts**
Distinguish EOSE from CLOSED/timeout readiness and flush buffered events
before resolving an EOSE fence.

**desktop/src/shared/theme/CommunityThemeController.tsx**
Seed and complete first-community migration only for confirmed absence;
uncertain hydration remains non-publishing.

**desktop/src/shared/theme/communityThemePreference.ts**
Keep the inherited appearance for the first migrated community and the
stable default for later empty communities.

**desktop/src/shared/theme/communityThemeSync.ts**
Arbitrate live and history results into valid, confirmed-absent,
invalid, or unavailable hydration outcomes.

**Tests**
Cover EOSE/CLOSED readiness, subscription failure, timeout, unreadable
and live-racing events, no-op initialization, and first-to-later
community fallback isolation.

</details>

## Reproduction steps

1. Save a non-default appearance for a community relay.
2. Remove the community locally, then open the same relay again through
onboarding.
3. Arrange for the saved replacement event to arrive live while the
initial history query returns empty.
4. Confirm the saved appearance remains selected and the client does not
publish the default theme over it.
5. On an account with no theme records, open a first empty community and
confirm its inherited appearance is migrated; open a later empty
community and confirm it starts from the stable default.

## Validation

- Pre-push desktop checks, typecheck, and full desktop tests: passed at
`f79556b0e`
- Focused theme/relay readiness tests: 38 passed
- Desktop file-size ratchet and diff check: passed

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
mobile-v0.10.0-rc.1
2026-08-11 08:21:34 -07:00
Taylor HoandGitHub 7eb8cc5a5f fix(link-preview): resolve YouTube videos through oEmbed (#5520)
**Category:** fix
**User Impact:** YouTube video links now resolve into reliable previews
instead of intermittently appearing as bare links.

**Problem:** Buzz intentionally reads at most **256 KiB** of page HTML
when building a generic link preview. The YouTube response that exposed
this bug was roughly **1.3 MiB**, with its Open Graph metadata beginning
around **686 KiB**—well beyond Buzz's bounded read—so extraction
returned no usable preview. YouTube can move that metadata between
responses, which explains why the same link may appear to work in one
build or request and fail in another; raising the generic cap would
increase bandwidth and allocation for every site while still scraping an
unstable application document.

**Solution:** Route recognized YouTube video URLs through YouTube's
structured oEmbed endpoint instead of parsing raw watch-page HTML. The
provider response is capped at **64 KiB** and retains Buzz's existing
HTTPS validation, pinned DNS/SSRF protection, disabled redirects,
timeouts, metadata bounds, and thumbnail sanitization. Provider failures
return no preview rather than falling back to fragile HTML scraping, and
embed URLs are canonicalized safely, including percent-encoded video
IDs.

<details>
<summary>File changes</summary>

**desktop/src-tauri/src/commands/link_preview.rs**
Recognizes supported YouTube URL forms, fetches bounded JSON metadata
from YouTube oEmbed, canonicalizes embed links, and adds response,
URL-boundary, malformed-data, resource-limit, and encoded-ID
regressions.

**desktop/src-tauri/Cargo.toml**
Declares percent decoding as a direct desktop dependency for safe
embed-ID canonicalization.

**desktop/src-tauri/Cargo.lock**
Records the direct dependency in the desktop package lock entry.

</details>

## Reproduction Steps

1. On the base branch, paste a YouTube URL whose Open Graph metadata
falls beyond the first 256 KiB of the raw watch-page response and
observe that no preview is produced.
2. Run this branch and paste a YouTube watch, mobile, music, `youtu.be`,
Shorts, live, or embed URL into the composer.
3. Confirm the preview resolves with the video's title, creator, and
sanitized thumbnail without downloading the full watch-page HTML.
4. Try an embed URL with a percent-encoded ID, such as
`https://www.youtube.com/embed/%64Qw4w9WgXcQ`, and confirm it resolves
to the same video.
5. Try a YouTube lookalike domain or an embed ID containing encoded
separators and confirm it is not routed through the provider path.

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
2026-08-11 08:03:01 -07:00
5e4d0fe925 fix(buzz-agent): harden Databricks OAuth token cache and callback (#5534)
Hardens the Databricks PKCE OAuth code in
`crates/buzz-agent/src/auth.rs`. Two fixes.

## Token cache is owner-only across its whole lifecycle, and race-safe

The PKCE cache holds both the access and refresh tokens, but `save()`
wrote it with a bare `fs::write` + `fs::rename`. Under a `022` umask the
file landed world-readable, and the fixed `*.json.tmp` temp name races
across concurrent savers sharing `$HOME` — one writer's `rename` can
fail on another's half-written temp.

**On write**, `write_private_cache()` creates a temp file with
owner-only permissions from the moment it exists — mode `0o600` on Unix
via `OpenOptions::mode` — writes and fsyncs it, then renames over the
destination. The rename swaps the inode wholesale, so a pre-existing
cache file with loose permissions is *replaced* by the new private inode
rather than inheriting its mode. `unique_suffix()` (getrandom, timestamp
fallback) gives each write a distinct temp name, and a drop guard
removes the temp on any failure path.

**On load**, owner-only is enforced as a cache lifecycle invariant, not
just a write-path property. A world-readable cache left by an older
buzz-agent was previously read straight into memory and returned on the
fresh cache-hit path without ever invoking `save()`, so a token file
with no advertised expiry could stay exposed indefinitely.
`read_cache()` now funnels every load — initial and cross-process
re-reads — through `read_private_cache()`, which on Unix opens with
`O_NOFOLLOW` (kernel-level symlink refusal, no stat/open TOCTOU),
requires a regular file, and `fchmod`s the pinned handle to `0o600` when
any group/other bit is set. A cache that cannot be secured is treated as
absent, so callers fail closed to a fresh flow rather than trusting an
exposed file.

## OAuth callback no longer reflects untrusted input

The localhost callback embedded the untrusted `error` query param
straight into the HTML response — an XSS sink on the redirect page — and
routed that same raw value into the error string that reaches the logs.

`callback_outcome()` is now a pure function returning `(result,
static_page)`: the browser always sees a fixed literal page that embeds
no request parameter, and failure detail travels only through the result
channel. `sanitize_callback_detail()` strips control characters (CR/LF
log-line injection) and caps length before that detail enters the error
string bound for the logs.

## Deferred: Windows owner-only ACLs

Windows owner-only protection is out of scope for this change. The
goose-parity route (`CreateFileW` with an owner-only SDDL
`D:P(A;;FA;;;OW)`) requires `unsafe` FFI, which this crate's
`#![forbid(unsafe_code)]` prohibits; reconciling that conflict is a
separate decision. Both platform seams — `create_private_temp_file`
(write) and `read_private_cache` (load) — have a `#[cfg(not(unix))]`
branch that relies on the default per-user ACLs and is the drop-in point
if Windows protection is added later. No new dependency and no `unsafe`
are introduced here.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Hayt <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz>
2026-08-11 09:58:15 -04:00
be48ce98bd fix(link-preview): reliably render previews sent right after they resolve (#5245)
## Overview

**Category:** fix  
**User impact:** Link previews no longer disappear when a message is
sent while preview metadata or media is still settling. Fast Enter,
rapid Enter, and confirmed-draft auto-send now preserve the preview
without duplicate sends or stale tags.

**Problem:** The composer could look ready before its sender-authored
snapshot tag existed. Send paths could then race preview
resolution/upload, while debounced preview state could attach a tag for
a URL that had already been removed. The same timing also caused
confirmed-draft auto-send to be consumed without sending.

**Solution:**
- Debounce preview resolution to avoid card flicker while typing, then
disable every submit path while a supported external preview settles. A
2-second escape cap still permits a bare-link send if resolution stalls.
- Keep submit synchronous: acquire a composer-local lock before
asynchronous send work, read ready tags from the live URL set, and
reject Enter/form submits while a snapshot is pending.
- Retry confirmed-draft auto-submit until preview settling clears, then
submit exactly once.
- Upload thumbnail and favicon independently. A failed upload shows a
toast and degrades to the surviving media (or text-only) rather than
leaving the card spinning.
- Exclude message-edit mode from preview resolution, upload, and Save
gating. Edit-time preview snapshots remain follow-up #5273.
- Canonicalize fragment-bearing URLs for preview lookup/snapshot
identity while preserving the original fragment links in message text.

## Link preview state walkthrough

Captured using PR #5245's actual public Open Graph metadata and artwork.
The deterministic E2E bridge controls only upload timing so the
transient disabled state can be captured reliably.

| State | Expected behavior | Screenshot |
| --- | --- | --- |
| **1. Snapshot upload pending** | The real PR preview is visible, but
Submit remains disabled until its sendable snapshot tag is ready. Click
and Enter cannot send a bare link during the settling window. | ![PR
5245 pasted with its real preview visible and Submit
disabled](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/5245/01-real-pasted-submit-disabled.png)
|
| **2. Snapshot ready** | Once snapshot upload settles and the tag is
ready, the same preview remains and Submit becomes active. | ![PR 5245
preview ready in the composer with Submit
enabled](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/5245/02-real-resolved-submit-enabled.png)
|
| **3. Message sent** | The sent event carries the snapshot tag and
renders the PR title, description, and artwork inline instead of
degrading to a bare URL. | ![PR 5245 real link preview rendered inline
in the message
list](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/5245/03-real-sent-preview-inline.png)
|

## Regression coverage

- Enter during metadata resolution or snapshot upload cannot send early.
- Paste-and-immediate-Enter sends after settling; rapid Enter submits
exactly once.
- Confirmed-draft auto-send waits for settling and fires exactly once.
- Removed/replaced URLs cannot leak stale snapshot tags or media refs.
- Thumbnail upload failure toasts and sends with the surviving favicon.
- Edit mode does not resolve/upload previews or gate Save.
- Fragment variants share a canonical preview while original fragment
links remain clickable.
- Existing ready-preview, suppression, bare-link fallback, and
multi-preview behavior remains covered.

## Reproduction steps

1. Open a channel and paste a supported external URL into the composer.
2. Press Enter immediately, before preview metadata/media finishes
settling.
3. Before this fix, the event could be sent without its preview snapshot
(or confirmed-draft auto-send could be lost). With this fix, submit
waits behind the disabled state and fires once with the matching
snapshot tag.
4. Remove or replace the URL and press Enter inside the debounce window.
The sent event contains tags only for URLs still present in the
submitted content.

## Validation

All required PR checks are green, including Desktop Core, Desktop Smoke
E2E shards, Desktop E2E Integration shards, macOS build, security
checks, and DCO.

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-10 20:27:30 -07:00
Taylor HoandGitHub 7e6e9c547f fix(link-preview): restore Buzz entity link cards (#5494)
**Category:** fix
**User Impact:** Buzz pull request, issue, and repository links now show
compact, useful metadata cards in received messages, including messages
sent by agents and the CLI.

**Problem:** Sender-authored snapshots protect recipients from external
preview fetches, but that change also removed recipient-side cards for
trusted Buzz entity links when the sender did not attach snapshots.

**Solution:** Resolve recognized Buzz entities only against the active
relay and show signed repository identity, title, and compact builder
context with the current inline Buzz mark in the favicon slot, but
without avatars, thumbnails, or external image fetches. Entity metadata
wins over conflicting sender snapshots, while unsupported or unavailable
metadata retains a safe text fallback.

<details>
<summary>File changes</summary>

**desktop/playwright.config.ts**
Adds the entity-link regression spec to the smoke test project.

**desktop/src/features/messages/ui/useComposerLinkPreviews.tsx**
Treats recognized Buzz entity cards as complete without generating
snapshot tags and retains fallback cards when relay metadata is absent.

**desktop/src/shared/lib/useResolvedLinkPreviews.test.mjs**
Covers kind-scoped entity detection, trusted relay metadata, root-scoped
lifecycle queries, exact single-repository root binding, image-less
pending state, and fallback behavior.

**desktop/src/shared/lib/useResolvedLinkPreviews.ts**
Resolves signed repository, pull request, and issue metadata from the
active relay. Entity roots fail closed unless they carry exactly one
matching repository tag; lifecycle queries are root-scoped before
limits; successful metadata remains stable until relay/community reset,
and PR commit context uses the immutable root event rather than an
unindexed update query.

**desktop/src/shared/ui/compact-link-preview-attachment.tsx**
Uses Buzz repository identity as the compact card provider and avoids
reserving thumbnail space for image-less entity cards.

**desktop/src/shared/ui/markdown.tsx**
Routes message cards through the combined entity/snapshot preview hook.

**desktop/src/shared/ui/markdown/useMessageLinkPreviews.test.mjs**
Proves relay-authenticated entity metadata beats a forged sender
snapshot while preserving mixed-link content order.

**desktop/src/shared/ui/markdown/useMessageLinkPreviews.ts**
Combines recipient-resolved Buzz entities with sender-authored external
snapshots using explicit trust precedence and first-seen ordering.

**desktop/tests/e2e/entity-link-recipient-cards.spec.ts**
Exercises repository identity, PR workflow context, repository metadata,
image-less rendering, and composer send behavior for agent/CLI-style
entity links.

</details>

## Reproduction steps

1. Open a channel containing a message sent without `link-preview` tags
whose content includes valid `buzz://pr`, `buzz://issue`, or
`buzz://repo` links.
2. Confirm each card shows its repository identity and signed title;
PRs/issues also show compact lifecycle context, and repositories show
description/status/default branch.
3. Confirm the cards use the Buzz mark in the favicon slot with no
avatar, thumbnail, or reserved image area.
4. Compose and send a message containing a Buzz entity link; confirm
sending is not blocked waiting for a snapshot.
5. Send a message containing both a Buzz entity link and a
snapshot-backed HTTPS link; confirm cards follow content order and the
HTTPS link remains sender-snapshot-only.

## Screenshots

### Recipient view — Buzz-branded metadata cards

Repository identity, title, and compact builder context render with the
current inline Buzz mark in the favicon slot and no avatar, thumbnail,
or reserved image space.

![Recipient view showing Buzz-branded PR and repository
cards](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/5494/01-recipient-entity-cards-current-buzz-mark.png)

## Validation

At commit `7bc70b0a9f70392bd062ed25b1d2362cc4021a40` with a clean
working tree:

- Pre-push hooks passed: branch skew, desktop check, desktop typecheck,
and full desktop unit suite
- Full desktop unit suite: 4,560 passed
- Purpose-built Playwright regression after a fresh E2E build: 2 passed
- Screenshot regenerated from the same commit and visually inspected

Originating conversation: Buzz channel
`c2859932-b679-4091-9c7e-f5a65deddd64`, thread
`93c3e7be59a8d1ec10b4992efd783a2a79f253a10f10d39746c6ad41b0d5bb42`.

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
2026-08-10 18:38:38 -07:00
538e5e113f chore(release): release Buzz Desktop version 0.5.9 (#5521)
## Buzz Desktop release v0.5.9

- **Frozen main:** `f8f2ef0440e7a074223ec04dc3b32d817b8b9d9b`
- **Reviewed candidate:** `ee33722615ca1e7b8efb03e2ed641d99448c8899`
- **Previous desktop release:** `desktop-v0.5.8`
- **Proposed immutable tag:** `desktop-v0.5.9`

This PR may be **squash merged** after the Desktop Release Candidate
check and all protected-branch checks pass. Merging authorizes
publication of the exact reviewed candidate; later or unrelated changes
on `main` cannot alter it.

The checked-in changelog accounts for every non-merge commit in the
release range. The Desktop tag points to the reviewed candidate commit,
not the later squash commit. Publication remains bound to that immutable
candidate tag.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
2026-08-10 15:21:14 -07:00
f8f2ef0440 feat(cli): add --visibility flag to channels update (#5119)
## Why
`buzz channels update` could already change name, description, and TTL,
but the SDK/relay/DB path for channel visibility was unreachable from
the CLI.

## What
- Add `--visibility open|private` to `buzz channels update`
- Pass the visibility value through to `build_update_channel`
- Add guard tests proving empty updates still fail and visibility-only
updates are accepted

## Risk Assessment
Low — this is limited to the buzz-cli update command and uses existing
SDK validation plus existing relay/DB handling.

## References
- Spike notes: `RESEARCH/SPIKE_CHANNEL_VISIBILITY_TOGGLE.md`
- Local validation: `cargo test -p buzz-cli`

Generated with Codex

Signed-off-by: Cameron Hotchkies <chotchkies@block.xyz>
Co-authored-by: Lazy Joe <dbd8c9941ba6dafebcef0abc015b65e75d52e7452f2ce483c9c3fd4d180f2504@buzz.block.builderlab.xyz>
2026-08-10 14:48:30 -07:00
3f2f32641f Polish desktop onboarding flow (#5310)
## Summary
- standardize onboarding navigation and horizontal step transitions
- refine the avatar editor with live preview, segmented modes, search,
skin tones, and reduced-motion-safe feedback
- simplify harness/default-model actions and supporting copy

## Testing
- desktop typecheck and static guards
- desktop E2E build
- 9 focused onboarding smoke tests
- 4 focused onboarding/profile integration walkthroughs
- 4,535 desktop unit tests

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz>
2026-08-10 14:33:23 -07:00
07a3c768d6 fix(desktop): quiesce renderer polling while hidden (#3677) (#5490)
Fixes #3677.

## Problem

The renderer never quiesces: recurring timers, query polling, and
re-render tickers run at full rate whether the window is visible,
hidden, or minimized. Measured on a live installed app: **27.5% mean
renderer CPU visible vs 28.7% hidden** (60×1s `ps` samples of the
WebContent process; `sample(1)` dominated by
`WebCore::timerFired`/ThreadTimers, microtask checkpoints, JSON parsing,
style matching). Matches all three reproductions in #3677 (macOS
prerelease, Linux/WebKitGTK A/B/A minimize test, stable macOS).

Per-timer instrumentation (dev build, wrapped
`setInterval`/`setTimeout`/rAF) attributed the recurring work: `useNow`
60 fires/min, 40 active TanStack refetch intervals, agent-turn pruning
12/min, auto-restart ticks, huddle/reminder polls — none
visibility-gated.

## Fix (two-tier gating, standard mechanisms only)

Two separate signals in `desktop/src/shared/lib/useDocumentVisible.ts`,
because they mean different things and (see residuals) are delivered
differently on macOS:

- **`useDocumentVisible`** — true Page Visibility only
(`document.visibilityState`). Gates local UI work that must keep running
on a visible-but-unfocused window: `useNow` relative clocks, agent-turn
pruning, huddle bar state/model-status polling, auto-restart tick.
Hidden ⇒ paused; `useNow` snaps to fresh `Date.now()` on return.
- **`useAppFocused`** — visible AND `document.hasFocus()`. Gates network
refetch polling only (`useFocusedRefetchInterval`, ~15 query families:
forum/home/agents/channels/templates/emoji/user-status/projects/workflows/persona-catalog/pulse/presence-list).
TanStack's `focusManager` is wired to this signal (idempotent, single
install) with `refetchOnWindowFocus: true`, so stale queries refresh
promptly on return. Deliberate side effect, documented in code: query
retries pause on blur; mutations and the presence heartbeat (`retry: 0`)
are unaffected.
- **Never gated:** reminder due-notification poll (fires while
hidden/unfocused — extracted to `reminderNotificationPoll.ts` with
regression test), huddle pipeline hot-start (`check_pipeline_hotstart`
survives backgrounding for the duration of a huddle), relay stall
watchdog, presence heartbeat. Live WebSocket delivery untouched
throughout.
- Huddle model-status indicator now clears only on huddle phase end, not
on visibility/focus changes.

## Validation

- Instrumented dev build, populated channel, fires/min:
**visible+focused** unchanged (`useNow 60 / prune 12 / watchdog 6 /
query 4 / auto-restart 4 / low-rate huddle/reminder/presence`);
**visible+blurred**: query polls 0, UI clocks continue (`useNow 60 /
prune 12`), reminders 2, presence live; **truly hidden**: only watchdog
6, reminders 2, presence ~2 — everything else 0. Return restored
visible+focused, selection preserved, queries refreshed.
- Hide-vs-blur decomposition (instrumented probe instance,
AppleScript-driven): on macOS WKWebView, Cmd-H / minimize / full
occlusion did **not** reliably produce `visibilityState === "hidden"` —
they reliably produced focus loss. The CPU-dominant quiescence path on
macOS is therefore the focus gate; the visibility gate is exercised
fully on platforms that report hidden (e.g. WebKitGTK minimize per the
Linux repro).
- Gate-regression tests: signal separation, `useNow` hidden-pause +
fresh-snap on return, focus-gated interval pause/resume-with-refresh,
reminder delivery while hidden+unfocused (5 new, plus primitive wiring
tests).
- Push gate: desktop check, typecheck, full desktop suite **4549/4549**
at `1237548d1`.

## Known residuals

- **macOS hidden-signal limitation:** because WKWebView rarely reports
`hidden` on app-hide/minimize, hidden-only consumers (`useNow`, prune,
huddle UI polls) may keep ticking on macOS when the app is hidden. These
are cheap local timers; the expensive network polling still quiesces via
focus loss, which is what the measured 28% CPU was attributed to. If the
residual local-timer cost proves measurable, the follow-up is bridging
Tauri window hidden/minimized events into the visibility signal.
- End-to-end CPU confirmation on a packaged build is the post-merge
follow-up (against the 28% idle baseline).
- Visible-state costs (skeleton animation pileups on stuck loading
views, per-poll JSON payload churn) are intentionally out of scope —
separate follow-up issue.

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Meeseeks <2e96988f190ed1bd3c568760103aa4cadb2bc6195b832e252c984392c89039bd@buzz.block.builderlab.xyz>
2026-08-10 11:42:31 -07:00
2777189d96 fix(channels): restore member invitations to private channels (#5493)
## Summary

- restore private-channel invitations for every active member
- keep owner/admin-only enforcement for elevated role grants, active
role changes, and removals
- preserve #4612's unrelated Desktop/mobile failure handling and
hardening
- add relay coverage for the ordinary actor/target role matrix
(`member`, `guest`, `bot`)

## Validation

- pre-push hook passed on `7de700e17642ad7e10155f9537033168d9249268`:
branch skew, Desktop checks/typecheck/tests/Tauri checks, mobile tests,
and Rust tests
- `cargo test -p buzz-test-client --test e2e_relay --no-run`
- `cargo fmt --all -- --check`
- `git diff --check`
- Donut and Mongo independently reviewed the cross-layer authorization
behavior; Donut's role-matrix coverage finding is addressed in this
revision

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-10 10:59:22 -07:00
5a3b3d2322 perf(ci): experiment with sccache for relay builds (#5224)
## Summary

- add a SHA-pinned sccache action to reuse unchanged Rust compilation
units when the exact relay artifact cache misses
- keep pull requests read-only while preserving cache writes for trusted
`main` and `release` pushes
- stop saving isolated exact relay-artifact caches from PRs, reducing
cache churn
- preserve the exact artifact cache as the zero-build fast path

## Why this is an experiment

The relay artifact job currently misses its exact cache whenever any
file under `crates/**` changes, forcing a full workspace rebuild. PR
#4975 spent roughly 21 minutes in that job for a one-file `buzz-sdk`
change. sccache targets the relevant reuse boundary—individual compiler
inputs—but the repository cache pool is already under heavy eviction
pressure, so this PR does **not** claim a proven timing win yet.

## Safety

- `Mozilla-Actions/sccache-action` is pinned to commit
`fc920bf0ec8de6ee65d409111f7ec508035751ba`
- `RUSTC_WRAPPER` is scoped only to `Build relay artifacts`
- PRs use `READ_ONLY`; trusted `push` runs (`main` and `release`) use
`READ_WRITE`
- the existing exact finished-artifact cache remains the first/fast path
- finished artifacts are saved only by trusted pushes, preserving the
former trust boundary
- workflow permissions remain `contents: read`; no `pull_request_target`
path is introduced
- the pinned action automatically emits sccache
hit/miss/error/write/duration statistics in its post-job hook

## Validation

- `actionlint .github/workflows/ci.yml`
- `git diff --check`
- desktop release-cache contract test
- release-ref contract test
- independent code-shape reviews from Princess Donut and Mongo: 9/10, no
remaining findings

## Measurement plan

1. purge obsolete PR-scoped `relay-artifacts-*` cache entries before
measurement
2. merge/push a trusted writer to populate sccache
3. run a representative one-crate PR
4. compare relay job duration and automatic sccache statistics against
the 21–22 minute baseline
5. retain this only if the warm run demonstrates material improvement

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-10 10:49:47 -07:00
9c074bb89b fix(desktop): bound nine unbounded localStorage stores (#5454)
Part of #5418 (Phase 1, lane A). Companion to #5453 (TTL sweep).

## What

Nine localStorage stores grew without bound (full 58-call-site audit in
the tracking issue). Each now has an explicit leak-guard cap, applied
wherever the store is parsed, merged, or written, preserving each file's
merge/versioning semantics:

- **Community icons:** 32 entries, 96 KiB/value (aligned with the
relay's `MAX_WORKSPACE_ICON_DATA_URL_LEN`); touched relay becomes
newest.
- **Channel mutes/stars:** newest-500 cap each, bounded by recency
(`updatedAt`, channel-ID lexical tie-breaker), with the just-written
channel unconditionally preserved for that write (cap−1 recency slots +
the mutated key). A bounded LWW store cannot guarantee permanent
deletion history; the guarantee here is that **the just-written mutation
survives its own bounding** and, as the newest entry, defeats an older
remote `true` through the pre-publish `mergeStores`. Known residual
(accepted): `updatedAt` is whole-second, so two distinct mutations
inside the same second at exact capacity can still evict the earlier one
before the debounced publish — same root cause as the merge-path
same-second tie, tracked for the follow-up precision fix rather than
more preservation machinery. Enforced at parse, post-merge, local state,
and persistence.
- **Forced unread:** newest 500 insertion-ordered, touched channels
refreshed.
- **Persistent agent audiences:** 200-scope LRU. An unchanged-audience
touch (including re-initializing an existing scope) refreshes LRU order
and persists without advancing the scope's revision or emitting; an
already-most-recent touch is a pure no-op (no clone, no write), so
render-path re-initialization causes zero storage traffic.
- **Self profiles:** newest 8 per relay / 32 globally by `updatedAt`,
just-written key always preserved; trim count-gates before parsing
payloads so under-cap writes skip the scan entirely.
- **Sections:** newest 100 + newest 1,000 assignments, orphans removed;
`assignChannel` delete/reinserts the touched channel so a reassignment
becomes newest in insertion order and cannot be evicted by the next
assignment. **Sort prefs:** 104 groups (100 sections + 4 fixed).
- **Feature overrides:** `getOverrides()` filters to current-manifest
boolean ids on read only — no write-back from the render-path getter.

## Review-driven revisions

- `237f25e4` — three narrow changes from the first adversarial review
(no render-path storage write, icon cap aligned to relay constant,
count-gated profile trim).
- `d864ffb0` — fixes for the two GitHub review findings on `237f25e4`:
(P1) mute/star bounding switched from false-tombstone-first eviction to
pure recency, with regressions proving an at-capacity unmute/unstar
survives bounding and the pre-publish LWW merge; (P2) unchanged
agent-audience touches now refresh LRU order (no revision advance, no
emit), with a subscriber-mounted regression.
- `3ddbb26d` — MRU guard from the second adversarial VERIFY: the P2
touch path skips clone/persist entirely when the scope is already
most-recently-inserted, eliminating repeat synchronous localStorage
writes from render-path effects. Test proves a non-MRU identical touch
writes exactly once (scope persisted last) and an already-MRU touch
writes zero times.
- `e220ccd9` — fixes for the second GitHub review round (Carl, on Wes's
behalf): (1) mute/star bounders preserve the just-mutated key so a
same-second mutation at capacity survives its own bounding; merge/sync
call sites unchanged; (2) `assignChannel` delete/reinserts the touched
key so an at-capacity reassignment isn't evicted by the next new
assignment. Regressions at storage and hook level for both;
negative-control run of the 7 new tests against the old sources: 7 fail.

## Validation

- Full desktop suite 4555/4555 at both `d864ffb0` and `3ddbb26d`, plus
desktop-check/typecheck via the push gate; focused storage/audience
tests 62/62 at `d864ffb0`, 14/14 audience suite at `3ddbb26d`.
- Independent adversarial review: APPROVE at `88a55aee` (including 100
smoke E2E specs covering every seeded store, run manually since push
hooks exclude Playwright), then a second VERIFY pass: **VERIFIED at
`d864ffb0`** — P1/P2 confirmed closed via negative-control runs of the
new suites against the old sources, plus smoke Playwright on the
mute/star/audience specs (17 passed). That VERIFY requested one
pre-merge change (no localStorage writes from the render path), landed
as the narrow MRU guard in `3ddbb26d` within the reviewer's stated
no-re-review boundary. A third VERIFY pass: **VERIFIED at `e220ccd9`** —
both findings from the second GitHub review confirmed closed by
sensitivity testing (new tests fail on old sources), hostile same-call
section-trim case constructed and passed, full suite 4562/4562 re-run
independently.

Authored by Meeseeks (agent), reviewed by Beth (agent), integrated by
Rick (agent). Discussion: Buzz channel time-based-localstorage-eviction,
thread 0d85a73ca43e54748128f89c3512a4726131bf5473253395d46bf8f3a7b58bd4.

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Meeseeks <2e96988f190ed1bd3c568760103aa4cadb2bc6195b832e252c984392c89039bd@buzz.block.builderlab.xyz>
2026-08-10 10:39:07 -07:00
bb9aae1065 feat(desktop): time-based sweep for stale localStorage caches (#5453)
Part of #5418 (Phase 1, lane B).

## What

Adds a periodic, whitelist-driven TTL sweep for disposable localStorage
caches so a desktop session left open for days converges to the same
storage state as one restarted nightly.

- New `desktop/src/shared/lib/localStorageSweep.ts`: declarative
`LOCAL_STORAGE_SWEEP_RULES` table — six repaintable pure-cache prefixes
(matching `PURE_CACHE_KEY_PREFIXES` in `localStorageQuota.ts`), all
14-day TTL, keyed on each payload's `updatedAt` (user-label buckets use
their newest nested per-profile timestamp).
- Entries with no trustworthy timestamp are retained, never guessed
stale. `buzz-self-profile.v1:` is deliberately excluded — it is the
load-bearing offline identity fallback (guard comment in the table).
- Scheduler: first sweep deferred off the boot critical path via
`requestIdleCallback` (1.5s timeout) with a 250ms timer fallback, then
hourly and on return-to-visible, debounced to 5 minutes. Throw-safe
throughout (failures `console.warn`, never crash — per `safeStorage.ts`
conventions / #5078).
- Wired in `desktop/src/main.tsx` beside
`recoverLocalStorageQuotaOnStartup()`.

## Validation

- Focused node test 7/7 at HEAD; pre-push gate green (desktop-check,
desktop-typecheck, full desktop-test 4542/4542).
- Manual Playwright (not covered by push hooks):
`relay-connectivity.spec.ts -g "04"` (offline cached identity) passes
1/1 at HEAD — this spec caught and now guards the v1 regression.
- Independent adversarial review: FULL REVIEW (REQUEST CHANGES) then
VERIFIED — PASS at exactly this commit, including whitelist containment
against the 58-site inventory, scheduler tracing, and smoke E2E.

Authored by Summer (agent), reviewed by Beth (agent), integrated by Rick
(agent). Discussion: Buzz channel time-based-localstorage-eviction,
thread 0d85a73ca43e54748128f89c3512a4726131bf5473253395d46bf8f3a7b58bd4.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Summer <1fdd3cc104e2911eb3b2da6f97d1b25f4a7f3550ded4492b24ff1d95acd66766@buzz.block.builderlab.xyz>
2026-08-10 09:47:04 -07:00
43573d114b ci(release): gate OSS desktop auto-update promotion (#5398)
## Summary

Separate OSS desktop artifact publication from fleet-wide auto-update
promotion.

- retain the exact generated updater manifest as `updater-manifest.json`
on each immutable `desktop-vX.Y.Z` release
- stop the tag-triggered build from mutating
`buzz-desktop-latest/latest.json`
- add a `main`-only manual promotion workflow with one global
concurrency group
- validate stable semver, release/tag commit identity, draft/prerelease
state, exact platform set, signatures, version-bound asset URLs, asset
existence, monotonicity, idempotent retries, and a final stale-state
check before writing
- document the operator flow and pin the split with focused contract
tests

## Safety behavior

Publishing a versioned GitHub release no longer exposes it through the
in-app updater. Operators can install and test those exact
signed/notarized artifacts, then manually run **Promote OSS Desktop
Auto-Update** with the stable version.

Promotion rejects downgrades. A same-version retry succeeds only when
the rolling and candidate manifests are byte-identical. The workflow
re-reads the current rolling version immediately before its only write
and records the actor, source tag commit, previous version, manifest
digest, and run URL.

## Verification

Verified at commit `39caf1603be06bb476905225ec55f7bbbe86b237`:

```text
scripts/test-oss-desktop-promotion.sh
OSS desktop promotion contract passed

scripts/test-release-ref-contract.sh
release ref contract passed

git diff --check origin/main...HEAD
(clean)
```

The repository pre-push hook also passed `branch-skew` for the exact
pushed head; package suites were correctly skipped because this change
only touches release workflows, scripts, and documentation.

Originating conversation: Buzz channel `separate-publish-step-release`,
thread
`8857ce8bbe928e891165eddcf06c666cf6eae16181c3f02a6d8c396d8a536026`.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-10 09:05:32 -07:00
c1e20a814b fix(release): pin desktop PR operations to block/buzz (#5212)
## Summary

Pin every GitHub CLI PR operation in the Desktop release helper to
`block/buzz`.

Without an explicit repository, `gh` refuses to create the release PR in
checkouts that have multiple GitHub remotes and no configured default.
This happens after the candidate has already been generated, validated,
committed, and pushed.

Add release-contract assertions covering the list, edit, and create
paths so repository qualification cannot regress.

## Validation

- `bash -n scripts/prepare-desktop-release.sh
scripts/test-release-ref-contract.sh`
- `scripts/test-release-ref-contract.sh`
- pre-push `branch-skew`

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-10 08:59:15 -07:00
3c76f682c3 fix(search): surface exact short profile names (#5480)
## Summary

- prioritize exact whole-lexeme matches within short kind-0 prefix
searches
- preserve the existing prefix result set, pagination, community/channel
scope, hydration, and authorization path
- add a Postgres regression where newer noisy `jm…` profiles saturate
the bounded page

## Why

Desktop mention autocomplete starts searching after one character. The
`jm` profile is indexed and matches both `jm:*` prefix search and
standard full-text search, but production prefix search returns a full
50-result page without it. Raw profile JSON supplies enough unrelated
`jm…` lexemes that newer equal-rank matches fill the bounded page before
the exact short display name.

Changing clients would leave deployed Desktop 0.5.8 installations
broken. This shared search-layer compatibility fix changes ordering only
for `Prefix + kinds:[0] + query length <= 2`; message search, longer
profile typeahead, and agent eligibility are untouched.

## Validation

At commit `ff88761135d5045139aeb3da14d08cbfba203169` with a clean
worktree:

- `BUZZ_TEST_DATABASE_URL=postgres://buzz:buzz_dev@localhost:5432/buzz
cargo test -p buzz-search --tests -- --include-ignored` — 22 passed (3
unit + 19 Postgres integration)
- `cargo clippy -p buzz-search --tests -- -D warnings`
- `cargo fmt --all -- --check`
- mutation check: disabling exact-lexeme priority makes
`short_kind0_prefix_prioritizes_exact_lexeme_on_a_noisy_page` fail
- mandatory pre-push hooks: branch-skew, Rust tests, and Desktop/Tauri
checks passed

## Risk

Low. The extra ordering predicate applies only to one- or two-character
prefix searches restricted exactly to kind 0. It does not add
candidates, bypass filters, or alter access control. Exact matches move
ahead of broader prefix matches; all remaining ordering stays relevance,
recency, then event ID.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-10 08:58:45 -07:00
563e4346da Reduce repeated ACP session context (#5423)
## Summary

- deliver legacy ACP standing context once per live session, committing
delivery state only after a successful turn
- send only new thread/DM event deltas on later turns, with fail-open
behavior for missing IDs and failed/cancelled prompts
- fence native steer delivery acknowledgements by ACP session identity
so stale acks cannot poison replacement sessions
- keep context hints truthful when a fetch contains only the triggering
event versus history delivered earlier

## Validation

The pre-push hook passed on exact pushed head
`6a768f1bc80fe63c686acf8d730f177fff8add3c`:

- `branch-skew`
- `desktop-check`
- `desktop-typecheck`
- `desktop-test`
- `rust-tests`
- `desktop-tauri-checks`

Focused regression tests were also run while iterating:

- `channel_prompt_commits_delivery_state_only_after_acp_success`
- `in_flight_stale_native_steer_ack_cannot_update_replacement_session`
- thread/DM trigger-only versus previously-delivered context hint tests

## Known limitations and follow-ups

A local Goose smoke timed out at `session/new`. This diff does not
change code that executes at or before `session/new`; its earliest
affected runtime behavior is delivery-state insertion after session
creation succeeds. The smoke failure is therefore bounded as
environmental or pre-existing, but no successful live-provider turn was
obtained. Scripted ACP wire/lifecycle tests carry the regression
coverage.

- #5421 — distinguish post-delta, already-delivered, and fetch-truncated
context counts
- #5422 — define a standing-context re-delivery policy if a legacy
provider compacts it away

Durable process-restart/session resume remains out of scope for this
slice of #5342. #5386 also remains separate pending upstream adapter
support.

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-10 08:50:34 -07:00
5e4c05f90b feat(desktop): NIP-AM agent-usage backend — P2 emission/transport/archive + P4a aggregation/D6 (#4000)
## What

Implements Phases 2 and 4a of the Usage v2 plan (plan events
`d0268cd0`/`0e95b035`), extending the archive backend to emit,
transport, archive, and aggregate both cache categories and billing
identity fail-closed.

### P2 — emission, transport, archive

**Tri-state accumulators** (`Unseen`/`Exact`/`Unknown`) for cache-read
and cache-write in `buzz-agent` turn and session state. Absent field =
Unknown (never zero) through the full pipeline. No `unwrap_or(0)` on the
cache path. Both cache folds are gated on usage-bearing responses (same
gate as the total-state and identity folds) — a response with no usage
at all must not poison either accumulator.

**Overflow-aware input token parsing and accumulation** — closed
end-to-end from parse through wire to ACP:
- `sum_usage()` returns `SumUsageResult` (`Exact(u64)` | `Overflow`) —
checked arithmetic, never clamps. `anthropic_input_tokens()` returns
`Option<SumUsageResult>` since it sums three fields (`input_tokens +
cache_read_input_tokens + cache_creation_input_tokens`) that can
collectively overflow. Single-field callers (`prompt_tokens`,
`completion_tokens`, etc.) convert via `.into_exact()` — their
single-field sums cannot overflow.
- `LlmResponse.input_tokens_overflowed: bool` propagates the parse-layer
signal into the run loop. When set, `input_tokens` is `None` (clamped
value discarded), the context-gate baseline
(`last_request_input_tokens`) is frozen at its prior reading, and
`turn_input_tokens` is poisoned to `TurnIOState::Poisoned` before any
emission — including mid-turn `emit_usage_update` calls. A dedicated
enum on `LlmResponse.input_tokens` would ripple into ~20 existing test
assertions on `r.input_tokens == Some(...)`; the bool flag confines the
change to the two call sites that check it.
- `TurnIOState` (`Unseen`/`Exact`/`Poisoned`) for input and output:
per-round fold uses `checked_add`; overflow poisons permanently at turn
and session level, no healing. Absence does not poison (pass-2-cleared
contract unchanged). Wire emission omits
`accumulatedInputTokens`/`accumulatedOutputTokens` when poisoned — never
null, never `u64::MAX`. ACP treats absent = publisher-poisoned:
`delta_reliable: false`, null turn fields, null cumulative for that
category; session cumulative stays unknown for all subsequent turns once
poisoned.

**Conditional wire emission** for `accumulatedCachedInputTokens` and new
`accumulatedCacheWriteTokens`: fields are omitted when the cumulative is
Unseen or Unknown. ACP `_goose/unstable/session/update` contract
documented next to the payload with tests for all absence/zero variants.

**`PricingIdentity` stamping (publisher-side)**:
- `pricing_authority()`: canonical parsed-URL endpoint comparison
against the official allowlist — HTTPS only, exact allowlisted host
(lookalike-safe), default port (omitted or explicit :443), required API
base path, rejects userinfo/query/fragment/path-prefix lookalikes.
- Model: the actually-requested `request_model` after mesh/auto
resolution (not `effective_model_str`).
- Turn discipline: identity retained only while ALL usage in the current
turn carries one identical proven identity; any mismatch,
unproven-usage-bearing response, or unpaired cumulative snapshot poisons
to absent; a later matching notification does not heal a mixed turn.

**ACP `UsageTracker` identity fold**: per-in-flight-turn tri-state
identity accumulator replacing last-update-wins. Any absent identity on
a token-advancing notification or exact mismatch poisons to absent;
poison survives later updates; reset in `begin_turn()`/`take()`; reset
also when a request fails (baseline cleared so preflight gate cannot
stay frozen sub-threshold on retries).

**M3 migration**: adds `turn_cache_write_tokens`,
`cumulative_cache_write_tokens`, `pricing_authority`, `pricing_model`,
`pricing_cache_class` to `agent_metric_index`. Additive, idempotent,
guarded per-column by marker. M2 migration also guarded per-column (turn
and cumulative cache-read columns checked and added independently;
marker commits only after both are present). Fresh-DB schema includes
all columns.

**First-turn baselines**: `seed_zero_baseline` seeds `last_input:
Some(0)`, `last_output: Some(0)`, `last_cached_input: Some(0)`,
`last_cache_write: Some(0)`, and `last_total: Some(0)` — all have the
known-zero-at-spawn argument. Absent fields from incoming snapshots
still produce unknown (tri-state unchanged). Sessions buzz-acp did not
spawn (no seed) remain fail-closed on turn one.

**`ReportedUsage` TS mirror**: `cacheReadTokens`, `cacheWriteTokens`,
`freshInputTokens` added to `tauriArchive.ts` as `UsageField` members,
field-for-field with the Rust struct.

### P4a — aggregation layer

**Extended S-1 ladder** to cache-read and cache-write via the same
`ladder_token` path as the existing token fields.

**`freshInputTokens` derivation**: checked arithmetic, fail-closed —
absent cache fields produce Unknown (not zero), overflow and
`cacheRead+cacheWrite > input` both produce `incomplete: true`.
Aggregated as a `UsageField`.

**D6 comparator**: `sort_value()` = provider total when known, else
`input+output` when both known, else `None` (unknown-last). Replaces the
prior total-only comparator for both agent-level and model-level sort.
Ships a pinned test vector that the TS render layer (P5) must match.

## Test coverage

- `buzz-agent`: 440 lib + 15 integration (golden_transcripts) — includes
13 new `cache_total_state_tests`; 14 new `turn_io_state_tests`; 3 new
`sum_usage_*` tests (exact single-field, exact two-field, overflow
signals correctly); 3 new `parse_anthropic_*` tests (overflow flag set +
value cleared, normal sum no flag, absent usage no flag); end-to-end
golden transcript drives real subprocess with Anthropic-shaped
`input_tokens: u64::MAX, cache_read: 1` response and asserts
`accumulatedInputTokens` absent from the emitted `usage_update` — no
logic duplication; 3 wire pin tests; 4 `fold_pricing_identity_*` tests;
`pricing_authority()` explicit-:443 acceptance
- `buzz-acp`: 700 tests (691 lib + 9 integration) — 4 new usage tests
(absent input → unreliable+null; absent output → unreliable+null;
goose-shaped both present unchanged; poison mid-session); 3 ACP behavior
tests; 7 pool lifecycle tests
- Desktop (Rust): 2259+ tests — 14 new P4a pinned tests; 2 M3 round-trip
tests; 1 serde key-shape test; 2 M2 partial-schema migration tests;
first-turn cache round-trip test

## Related PRs

- P1 NIP-AM spec: [#4632](https://github.com/block/buzz/pull/4632)
- P3 pricing table: [#4629](https://github.com/block/buzz/pull/4629)
- UI (P5): [#4001](https://github.com/block/buzz/pull/4001)

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-08-10 10:47:37 -04:00
44456e200e fix(desktop): resolve overlapping member mentions (#5225)
## Why
Selecting or typing a member whose display name extends another member's
name, such as `@Fast Fizz Codex`, could emit p-tags for both identities
and wake the wrong agent.

## What
- Resolve overlapping member-name matches by choosing the longest valid
display name at each mention offset
- Preserve separately typed short-name mentions at different offsets
- Add regression coverage for selected team expansions and manually
typed prefix collisions

## Risk Assessment
Low to medium — this changes Desktop mention routing only. Exact
mentions and distinct offsets remain supported; same-length ambiguous
display names remain conservatively tagged because text alone cannot
disambiguate them.

Will resolve https://github.com/block/buzz/issues/2909

Generated with Goose

Signed-off-by: Hardworking Honey <c5c455215c2506cb8ba776518cec804af62d3a0526e32d496a22072e395042b9@buzz.block.builderlab.xyz>
Signed-off-by: Atish Patel <atish@squareup.com>
Co-authored-by: Hardworking Honey <c5c455215c2506cb8ba776518cec804af62d3a0526e32d496a22072e395042b9@buzz.block.builderlab.xyz>
2026-08-10 07:20:54 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>Wes
119a84897f chore(deps): update react monorepo (#4441)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
|
[@types/react](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react)
([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react))
| [`19.2.17` →
`19.2.18`](https://renovatebot.com/diffs/npm/@types%2freact/19.2.17/19.2.18)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2freact/19.2.18?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2freact/19.2.17/19.2.18?slim=true)
|
|
[@types/react-dom](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react-dom)
([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom))
| [`19.2.3` →
`19.2.4`](https://renovatebot.com/diffs/npm/@types%2freact-dom/19.2.3/19.2.4)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2freact-dom/19.2.4?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2freact-dom/19.2.3/19.2.4?slim=true)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/1) for more information.

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/block/buzz).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4xMi4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
2026-08-09 09:52:17 -07:00
d2ebaa95a7 ci(security): allow retired relay pool advisory (#5404)
## Summary

- temporarily allow the informational `RUSTSEC-2026-0243` advisory for
the retired `nostr-relay-pool` crate
- document the exact MeshLLM → `nostr-sdk 0.44.1` transitive path and
removal condition
- keep every other advisory and the global dependency policy enforced

## Why an exception

RustSec provides no patched `nostr-relay-pool` release because the
standalone crate was absorbed into `nostr-sdk >= 0.45`. Buzz inherits it
through pinned MeshLLM v0.74. A direct test bump to `nostr-sdk 0.45.1`
removed the retired crate but produced 13 MeshLLM API compilation
errors, so the durable fix requires an upstream source migration rather
than a lockfile update.

This narrow exception restores the required Security check while that
migration is completed. It must be removed once MeshLLM adopts
`nostr-sdk >= 0.45`.

## Validation

- `bin/cargo-deny --locked check --config deny.toml advisories`
- `bin/cargo-deny --locked check`
- `git diff --check origin/main...HEAD`
- mandatory pre-push Rust and desktop/Tauri checks

## Scope

One four-line `deny.toml` addition. No Rust source, lockfile, runtime,
or release behavior changes.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-09 09:39:21 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
c923e89a4b chore(deps): update dependency @tanstack/react-virtual to v3.14.9 (#4439)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@tanstack/react-virtual](https://tanstack.com/virtual)
([source](https://redirect.github.com/TanStack/virtual/tree/HEAD/packages/react-virtual))
| [`3.14.8` →
`3.14.9`](https://renovatebot.com/diffs/npm/@tanstack%2freact-virtual/3.14.8/3.14.9)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@tanstack%2freact-virtual/3.14.9?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@tanstack%2freact-virtual/3.14.8/3.14.9?slim=true)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/1) for more information.

---

### Release Notes

<details>
<summary>TanStack/virtual (@&#8203;tanstack/react-virtual)</summary>

###
[`v3.14.9`](https://redirect.github.com/TanStack/virtual/blob/HEAD/packages/react-virtual/CHANGELOG.md#3149)

[Compare
Source](https://redirect.github.com/TanStack/virtual/compare/@tanstack/react-virtual@3.14.8...@tanstack/react-virtual@3.14.9)

##### Patch Changes

- Updated dependencies
\[[`a5417b4`](https://redirect.github.com/TanStack/virtual/commit/a5417b4b0d3c82876747bb9635db7239c28d3e44)]:
-
[@&#8203;tanstack/virtual-core](https://redirect.github.com/tanstack/virtual-core)@&#8203;3.17.7

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/block/buzz).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4zLjIiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-09 09:11:47 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
856cdb848b chore(deps): update all non-major dependencies (#3049)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) | Type |
Update |
|---|---|---|---|---|---|
|
[@isomorphic-git/lightning-fs](https://redirect.github.com/isomorphic-git/lightning-fs)
| [`4.6.2` →
`4.6.3`](https://renovatebot.com/diffs/npm/@isomorphic-git%2flightning-fs/4.6.2/4.6.3)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@isomorphic-git%2flightning-fs/4.6.3?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@isomorphic-git%2flightning-fs/4.6.2/4.6.3?slim=true)
| dependencies | patch |
|
[@vitejs/plugin-react](https://redirect.github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#readme)
([source](https://redirect.github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react))
| [`6.0.3` →
`6.0.5`](https://renovatebot.com/diffs/npm/@vitejs%2fplugin-react/6.0.3/6.0.5)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@vitejs%2fplugin-react/6.0.5?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vitejs%2fplugin-react/6.0.3/6.0.5?slim=true)
| devDependencies | patch |
|
[@vitejs/plugin-react](https://redirect.github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#readme)
([source](https://redirect.github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react))
| [`6.0.3` →
`6.0.5`](https://renovatebot.com/diffs/npm/@vitejs%2fplugin-react/6.0.3/6.0.5)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@vitejs%2fplugin-react/6.0.5?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vitejs%2fplugin-react/6.0.3/6.0.5?slim=true)
| dependencies | patch |
| [dorny/paths-filter](https://redirect.github.com/dorny/paths-filter) |
`v4.0.2` → `v4.0.3` |
![age](https://developer.mend.io/api/mc/badges/age/github-tags/dorny%2fpaths-filter/v4.0.3?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/github-tags/dorny%2fpaths-filter/v4.0.2/v4.0.3?slim=true)
| action | patch |
| [isomorphic-git](https://isomorphic-git.org/)
([source](https://redirect.github.com/isomorphic-git/isomorphic-git)) |
[`1.38.7` →
`1.38.10`](https://renovatebot.com/diffs/npm/isomorphic-git/1.38.7/1.38.10)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/isomorphic-git/1.38.10?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/isomorphic-git/1.38.7/1.38.10?slim=true)
| dependencies | patch |
| [postcss](https://postcss.org/)
([source](https://redirect.github.com/postcss/postcss)) | [`8.5.19` →
`8.5.26`](https://renovatebot.com/diffs/npm/postcss/8.5.19/8.5.26) |
![age](https://developer.mend.io/api/mc/badges/age/npm/postcss/8.5.26?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/postcss/8.5.19/8.5.26?slim=true)
| devDependencies | patch |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/1) for more information.

---

### Release Notes

<details>
<summary>isomorphic-git/lightning-fs
(@&#8203;isomorphic-git/lightning-fs)</summary>

###
[`v4.6.3`](https://redirect.github.com/isomorphic-git/lightning-fs/releases/tag/v4.6.3)

[Compare
Source](https://redirect.github.com/isomorphic-git/lightning-fs/compare/v4.6.2...v4.6.3)

##### Bug Fixes

- IDB interface
([#&#8203;127](https://redirect.github.com/isomorphic-git/lightning-fs/issues/127))
([035e472](https://redirect.github.com/isomorphic-git/lightning-fs/commit/035e4725b9e6aa72d10cadc5ace20dec7ac76afb))

</details>

<details>
<summary>vitejs/vite-plugin-react
(@&#8203;vitejs/plugin-react)</summary>

###
[`v6.0.5`](https://redirect.github.com/vitejs/vite-plugin-react/blob/HEAD/packages/plugin-react/CHANGELOG.md#605-2026-07-30)

[Compare
Source](https://redirect.github.com/vitejs/vite-plugin-react/compare/f4b549822ec239799d746c030abb0b9a7d8f0a04...68c0cb8796ce18bd049c3d05c5210eaf0617eac0)

##### Fixed the react compiler preset filter to be linear
([#&#8203;1353](https://redirect.github.com/vitejs/vite-plugin-react/pull/1353))

The improved filter in v6.0.3 was non-linear and caused a performance
regression
([#&#8203;1349](https://redirect.github.com/vitejs/vite-plugin-react/issues/1349)).
The filter was changed to be linear to avoid that.

###
[`v6.0.4`](https://redirect.github.com/vitejs/vite-plugin-react/blob/HEAD/packages/plugin-react/CHANGELOG.md#604-2026-07-22)

[Compare
Source](https://redirect.github.com/vitejs/vite-plugin-react/compare/640fd358a0e82393acfce4e92e19a6ac6e1641a7...f4b549822ec239799d746c030abb0b9a7d8f0a04)

##### Fixed `$RefreshSig$ is not defined` error when running `vite dev`
with `NODE_ENV=production`

When running `vite dev` with `NODE_ENV=production`, the app errored with
`$RefreshSig$ is not defined`.
This error is now fixed.

</details>

<details>
<summary>dorny/paths-filter (dorny/paths-filter)</summary>

###
[`v4.0.3`](https://redirect.github.com/dorny/paths-filter/blob/HEAD/CHANGELOG.md#v403)

[Compare
Source](https://redirect.github.com/dorny/paths-filter/compare/v4.0.2...v4.0.3)

- [Document safe handling of file list outputs in
workflows](https://redirect.github.com/dorny/paths-filter/pull/326)
- [Escape multi-line filenames in list-files shell and csv
output](https://redirect.github.com/advisories/GHSA-7hc6-8hq5-9q2m)
- [Add 'some-with-excludes' predicate
quantifier](https://redirect.github.com/dorny/paths-filter/pull/322)
- [Add contents permission to PR
example](https://redirect.github.com/dorny/paths-filter/pull/248)
- [Scope base-ignored warning to API
path](https://redirect.github.com/dorny/paths-filter/pull/319)
- [Update outputs in readme to account for the 'every'
predicate-quantifier](https://redirect.github.com/dorny/paths-filter/pull/247)

</details>

<details>
<summary>isomorphic-git/isomorphic-git (isomorphic-git)</summary>

###
[`v1.38.10`](https://redirect.github.com/isomorphic-git/isomorphic-git/releases/tag/v1.38.10)

[Compare
Source](https://redirect.github.com/isomorphic-git/isomorphic-git/compare/v1.38.9...v1.38.10)

##### Bug Fixes

- **statusMatrix:** do not traverse symlinks in GitWalkerFs
([#&#8203;1215](https://redirect.github.com/isomorphic-git/isomorphic-git/issues/1215))
([#&#8203;2382](https://redirect.github.com/isomorphic-git/isomorphic-git/issues/2382))
([90ea101](https://redirect.github.com/isomorphic-git/isomorphic-git/commit/90ea101d329daa84b99cc0140a6275896ebbaf68))

###
[`v1.38.9`](https://redirect.github.com/isomorphic-git/isomorphic-git/releases/tag/v1.38.9)

[Compare
Source](https://redirect.github.com/isomorphic-git/isomorphic-git/compare/v1.38.8...v1.38.9)

##### Bug Fixes

- Preserve binary files when writing conflicted working tree
([#&#8203;2380](https://redirect.github.com/isomorphic-git/isomorphic-git/issues/2380))
([b41b1ab](https://redirect.github.com/isomorphic-git/isomorphic-git/commit/b41b1abc3df87326e639b49d0694915540d6dfb5))

###
[`v1.38.8`](https://redirect.github.com/isomorphic-git/isomorphic-git/releases/tag/v1.38.8)

[Compare
Source](https://redirect.github.com/isomorphic-git/isomorphic-git/compare/v1.38.7...v1.38.8)

##### Bug Fixes

- unsafe symlink from cherry pick
([#&#8203;2377](https://redirect.github.com/isomorphic-git/isomorphic-git/issues/2377))
([4664c8e](https://redirect.github.com/isomorphic-git/isomorphic-git/commit/4664c8e1147c3c7ba87c027e92093d28607ef4c0))

</details>

<details>
<summary>postcss/postcss (postcss)</summary>

###
[`v8.5.26`](https://redirect.github.com/postcss/postcss/blob/HEAD/CHANGELOG.md#8526)

[Compare
Source](https://redirect.github.com/postcss/postcss/compare/8.5.25...8.5.26)

- Fixed `list.split()` regression (by
[@&#8203;lazerg](https://redirect.github.com/lazerg)).
- Track symlinks in path protection in source map loading (by
[@&#8203;drengir1](https://redirect.github.com/drengir1)).

###
[`v8.5.25`](https://redirect.github.com/postcss/postcss/blob/HEAD/CHANGELOG.md#8525)

[Compare
Source](https://redirect.github.com/postcss/postcss/compare/8.5.24...8.5.25)

- Fixed 8.5.17 visitor regression.
- Fixed `list.split()` for non-string values (by
[@&#8203;amir-rezaei](https://redirect.github.com/amir-rezaei)).

###
[`v8.5.24`](https://redirect.github.com/postcss/postcss/blob/HEAD/CHANGELOG.md#8524)

[Compare
Source](https://redirect.github.com/postcss/postcss/compare/8.5.23...8.5.24)

- Preserve the BOM after the processing (by
[@&#8203;hdimer](https://redirect.github.com/hdimer)).

###
[`v8.5.23`](https://redirect.github.com/postcss/postcss/blob/HEAD/CHANGELOG.md#8523)

[Compare
Source](https://redirect.github.com/postcss/postcss/compare/8.5.22...8.5.23)

- Do not load source map without `opts.from` for security reasons.

###
[`v8.5.22`](https://redirect.github.com/postcss/postcss/blob/HEAD/CHANGELOG.md#8522)

[Compare
Source](https://redirect.github.com/postcss/postcss/compare/8.5.21...8.5.22)

- Fixed custom property losing semicolon before a comment (by
[@&#8203;sarathfrancis90](https://redirect.github.com/sarathfrancis90)).

###
[`v8.5.21`](https://redirect.github.com/postcss/postcss/blob/HEAD/CHANGELOG.md#8521)

[Compare
Source](https://redirect.github.com/postcss/postcss/compare/8.5.20...8.5.21)

- Fixed childless at-rule losing semicolon before comment (by
[@&#8203;sarathfrancis90](https://redirect.github.com/sarathfrancis90)).
- Fixed docs (by [@&#8203;isker](https://redirect.github.com/isker)).

###
[`v8.5.20`](https://redirect.github.com/postcss/postcss/blob/HEAD/CHANGELOG.md#8520)

[Compare
Source](https://redirect.github.com/postcss/postcss/compare/8.5.19...8.5.20)

- Fixed missing space if `AtRule#params` is set after (by
[@&#8203;sarathfrancis90](https://redirect.github.com/sarathfrancis90)).
- Fixed mixing AST error on warnings (by
[@&#8203;MahinAnowar](https://redirect.github.com/MahinAnowar)).

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/block/buzz).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yODAuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjEyLjAiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-09 09:11:26 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
e1ff91ecc1 chore(deps): update rust crate anyhow to v1.0.104 (#4447)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [anyhow](https://redirect.github.com/dtolnay/anyhow) | dependencies |
patch | `1.0.103` → `1.0.104` |
| [anyhow](https://redirect.github.com/dtolnay/anyhow) |
workspace.dependencies | patch | `1.0.103` → `1.0.104` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/1) for more information.

---

### Release Notes

<details>
<summary>dtolnay/anyhow (anyhow)</summary>

###
[`v1.0.104`](https://redirect.github.com/dtolnay/anyhow/releases/tag/1.0.104)

[Compare
Source](https://redirect.github.com/dtolnay/anyhow/compare/1.0.103...1.0.104)

- Update `syn` dev-dependency to version 3

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/block/buzz).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4zLjIiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-09 09:09:50 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
08de85c592 chore(deps): update rust crate arc-swap to v1.9.2 (#4448)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [arc-swap](https://redirect.github.com/vorner/arc-swap) | dependencies
| patch | `1.9.1` → `1.9.2` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/1) for more information.

---

### Release Notes

<details>
<summary>vorner/arc-swap (arc-swap)</summary>

###
[`v1.9.2`](https://redirect.github.com/vorner/arc-swap/blob/HEAD/CHANGELOG.md#192)

- Document RefCnt must not panic
([#&#8203;208](https://redirect.github.com/vorner/arc-swap/issues/208)).

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/block/buzz).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4zLjIiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-09 09:09:26 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
12b1f56648 chore(deps): update rust crate async-trait to v0.1.91 (#4458)
This PR contains the following updates:

| Package | Type | Update | Change | Pending |
|---|---|---|---|---|
| [async-trait](https://redirect.github.com/dtolnay/async-trait) |
dependencies | patch | `0.1.89` → `0.1.91` | `0.1.92` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/1) for more information.

---

### Release Notes

<details>
<summary>dtolnay/async-trait (async-trait)</summary>

###
[`v0.1.91`](https://redirect.github.com/dtolnay/async-trait/compare/0.1.90...0.1.91)

[Compare
Source](https://redirect.github.com/dtolnay/async-trait/compare/0.1.90...0.1.91)

###
[`v0.1.90`](https://redirect.github.com/dtolnay/async-trait/releases/tag/0.1.90)

[Compare
Source](https://redirect.github.com/dtolnay/async-trait/compare/0.1.89...0.1.90)

- Update to syn 3

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/block/buzz).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4xMi4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-09 09:08:58 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
d7cc724fa5 chore(deps): update rust crate diffy to v0.5.1 (#4466)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [diffy](https://redirect.github.com/bmwill/diffy) | dependencies |
patch | `0.5.0` → `0.5.1` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/1) for more information.

---

### Release Notes

<details>
<summary>bmwill/diffy (diffy)</summary>

###
[`v0.5.1`](https://redirect.github.com/bmwill/diffy/blob/HEAD/CHANGELOG.md#051---2026-07-18)

[Compare
Source](https://redirect.github.com/bmwill/diffy/compare/0.5.0...0.5.1)

##### Fixed

- [#&#8203;85](https://redirect.github.com/bmwill/diffy/pull/85)
  Merge conflict markers are now always placed on their own lines.
  Previously, a conflicting hunk at the end of a file without a trailing
  newline glued the next marker onto its last content line, producing
  unparseable output. This matches `git merge-file --diff3` behavior.

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/block/buzz).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4zLjIiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-09 09:08:38 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
7dd8791d07 chore(deps): update rust crate async-compression to v0.4.43 (#4456)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[async-compression](https://redirect.github.com/Nullus157/async-compression)
| dependencies | patch | `0.4.42` → `0.4.43` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/1) for more information.

---

### Release Notes

<details>
<summary>Nullus157/async-compression (async-compression)</summary>

###
[`v0.4.43`](https://redirect.github.com/Nullus157/async-compression/releases/tag/async-compression-v0.4.43)

[Compare
Source](https://redirect.github.com/Nullus157/async-compression/compare/async-compression-v0.4.42...async-compression-v0.4.43)

##### Other

- Fix hang when decoding a corrupt subsequent zstd frame
([#&#8203;470](https://redirect.github.com/Nullus157/async-compression/pull/470))

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/block/buzz).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4xMi4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-09 09:08:08 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
e668c6bb49 chore(deps): update rust crate clap to v4.6.6 (#4465)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [clap](https://redirect.github.com/clap-rs/clap) | dependencies |
patch | `4.6.1` → `4.6.6` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/1) for more information.

---

### Release Notes

<details>
<summary>clap-rs/clap (clap)</summary>

###
[`v4.6.6`](https://redirect.github.com/clap-rs/clap/compare/clap_complete-v4.6.5...clap_complete-v4.6.6)

[Compare
Source](https://redirect.github.com/clap-rs/clap/compare/v4.6.5...v4.6.6)

###
[`v4.6.5`](https://redirect.github.com/clap-rs/clap/compare/clap_complete-v4.6.4...clap_complete-v4.6.5)

[Compare
Source](https://redirect.github.com/clap-rs/clap/compare/v4.6.4...v4.6.5)

###
[`v4.6.4`](https://redirect.github.com/clap-rs/clap/blob/HEAD/CHANGELOG.md#464---2026-07-21)

[Compare
Source](https://redirect.github.com/clap-rs/clap/compare/v4.6.3...v4.6.4)

##### Internal

- Update to syn v3

###
[`v4.6.3`](https://redirect.github.com/clap-rs/clap/blob/HEAD/CHANGELOG.md#463---2026-07-20)

[Compare
Source](https://redirect.github.com/clap-rs/clap/compare/v4.6.2...v4.6.3)

##### Fixes

- *(derive)* Allow `"literal".function()` as attribute values

###
[`v4.6.2`](https://redirect.github.com/clap-rs/clap/blob/HEAD/CHANGELOG.md#462---2026-07-15)

[Compare
Source](https://redirect.github.com/clap-rs/clap/compare/v4.6.1...v4.6.2)

##### Fixes

- *(help)* Say `alias` when there is only one

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/block/buzz).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4xMi4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-09 09:07:46 -07:00
97aa9e3185 fix(desktop): preserve Welcome banner dismissal (#5406)
## Summary

- remove the complete Welcome guidance surface when dismissal reaches
`hidden`
- preserve dismissal across the private and starter Welcome channels for
the active identity
- assert the starter channel's actual `welcome-everyone` title on
re-entry

## Why

PR #5330 introduced two deterministic Desktop E2E failures:

- the inner banner unmounted, but `welcome-composer-guidance-layer`
remained
- the re-entry test expected case-sensitive `Welcome` while navigating
to `welcome-everyone`

The state hook also scoped completion to channel IDs while `ChannelPane`
remounts during navigation. The Welcome guidance is one experience
spanning both Welcome channels, so completion now survives that remount
while remaining identity-scoped.

## Validation

At `b577eb42edffe889f63566f2457eacea720f3593`:

- `pnpm -C desktop typecheck`
- focused Biome check for all four changed files
- E2E build
- both `welcome-everywhere banner` integration tests repeated three
times: **6/6 passed**
- mandatory pre-push desktop check, typecheck, and full desktop unit
suite: **4,535 passed**
- `git diff --check`

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-09 09:05:47 -07:00
5bf78671f4 fix(agent): retry LLM completion on malformed 2xx JSON body (#5351)
## Problem

A provider can return HTTP 200 with a **truncated JSON body** — cleanly
closed connection, correct framing, content cut off mid-value. Both LLM
HTTP loops treated this as a terminal error on the first attempt:
`AgentError::Llm("json: EOF while parsing a value")`, surfaced as code
-32000 at the ACP boundary, killing the agent turn before it produced
anything.

Observed live in a tb2.1 bench trial (write-compressor, tb21-twins-1):
deepseek via OpenRouter returned a truncated body, the agent died
mid-prompt with 0 turns completed, and the trial scored 0 on a provider
hiccup.

Meanwhile the same loops already retry timeouts, 429s, 5xxs, 499s, and
mid-body stream stalls — a truncated-but-complete body was the one
transient upstream fault that fell through to terminal.

## Fix

In both `post()` and `openrouter_post()`
(`crates/buzz-agent/src/llm.rs`): when the fully-received success body
fails `serde_json::from_slice`, `continue` the **existing** retry loop
instead of returning terminal — same `MAX_RETRIES` (3) bound, same
`backoff_with_jitter`. On exhaustion, the error goes through
`terminal_llm_error` so it carries cumulative duration + attempt count
like every other retried failure (previously the `json:` error carried
neither).

`post_anthropic` routes through `post()`, so
Anthropic/OpenAI/Databricks/mesh and OpenRouter are all covered.

## Why this cannot re-run a tool call

Hard requirement: tool calls are not idempotent, and this change must
not introduce any possibility of replaying one.

1. **The retry lives inside the HTTP POST helper, below the parse
boundary.** Tool calls are only ever extracted from a *successfully
parsed* response value
(`parse_openai`/`parse_anthropic`/`parse_responses`, all downstream of
these helpers' `Ok` return). A malformed body never parses, therefore no
tool call was ever extracted from it, therefore nothing downstream of it
ever dispatched.
2. **What is re-sent is the completion request itself** — the identical
`body_bytes` captured once at function entry. Sending a completion
request executes no tools; it asks the model for the next message.
3. **Same safety class as existing behavior.** The loop already re-sends
this identical request on 429/5xx/timeout/stream-stall; this adds one
more transient-fault arm to the same loop with the same bytes.

## Tests

Three new tests mirroring the existing 499/dropped-connection fixtures
(raw `TcpListener` stubs):
- `post_retries_malformed_json_body_and_succeeds` — truncated 200 body
on attempt 1, valid JSON on attempt 2; asserts success and **exactly 2**
server-side requests
- `post_exhausts_retries_on_persistent_malformed_json` —
always-truncated body; asserts exactly `MAX_RETRIES` attempts and a
terminal error carrying `json:` + cumulative/attempt context
- `openrouter_post_retries_malformed_json_body_and_succeeds` — same
recovery through OpenRouter's separate loop

Full `cargo test -p buzz-agent` green at e7a5d7bb (430 lib + all
integration targets, 0 failures); `cargo fmt` + `clippy --all-targets`
clean.

Originating conversation: buzz-benchmarking channel, thread 397a992d.

Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
2026-08-08 17:29:06 -04:00
f029deafae fix(desktop): welcome banner overlap and missing dismiss control (#5330)
## Problem

The `WelcomeComposerGuidanceLayer` in the `#Welcome` channel was
positioned with `absolute inset-x-0 bottom-full z-[-1]` — outside the
`composerWrapperRef` measurement boundary. `useComposerHeightPadding`
observes `composerWrapperRef`'s block size to set `paddingBottom` on the
timeline scroll container, but the absolutely-positioned layer didn't
contribute to that size. The banner sat directly on top of the newest
message, blocking the thread affordance on that message, and had no
manual dismiss control.

## Fix

**Overlap**: Changed `WelcomeComposerGuidanceLayer` from `absolute
inset-x-0 bottom-full z-[-1]` to `relative` (in normal flow). As a
normal-flow child of `composer-dock`, the layer's full height is now
measured by the ResizeObserver and fed into the timeline's
`paddingBottom`, so the newest message is always fully visible and its
thread affordance is always clickable while the banner shows.

**Dismiss**: Added an `X` close button
(`data-testid="welcome-composer-dismiss-button"`) on the prompt state.
Clicking fires `onDismiss`, which drives `dismissing → hidden`
immediately (same slide-down animation as the auto-dismiss path) and
marks the channel ID as completed in the session ref so the banner does
not reappear on channel re-entry within the session.

**Refactor**: Extracted the banner state machine (refs, timers,
`useEffect`s, and callbacks) from `ChannelPane.tsx` into
`useWelcomeComposerBanner.ts`. This keeps `ChannelPane.tsx` well under
the 1000-line file-size ratchet and makes the state machine
independently testable.

## Changed files

- `desktop/src/features/channels/ui/WelcomeComposerBanner.tsx` —
`WelcomeComposerGuidanceLayer` positioning fix; `onDismiss` prop;
dismiss button; `overflow-hidden` / `mb-0` / `flex-1` cleanup
- `desktop/src/features/channels/ui/ChannelPane.tsx` — remove inline
banner state machine, use `useWelcomeComposerBanner` hook, pass
`onDismiss`
- `desktop/src/features/channels/ui/useWelcomeComposerBanner.ts` — new
hook owning all banner state

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Hayt <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz>
2026-08-08 13:00:09 -04:00
fbf89e3bed fix(desktop): prevent horizontal clipping in Prompt Context modal (#5324)
The Prompt Context modal (observer feed → check icon under sent
messages) was clipping all content and card right-padding at the dialog
edge.

**Root cause**: `PromptContextDialog` renders inside `DialogContent`,
which is a CSS grid. The child flex wrapper had default `min-width:
auto`, so the widest unbreakable token in the content (64-char hex event
IDs, `Tags: [[...]]` JSON) set the grid track width, blowing it past
`max-w-xl`. `overflow-hidden` then clipped everything at the dialog edge
— including the section cards' right padding.

**Fix**:
- `AgentSessionTranscriptList.tsx`: add `min-w-0` to the `flex
max-h-[85vh] flex-col` wrapper so the grid item can shrink below its
max-content width.
- `PromptSectionAccordion.tsx`: replace `wrap-break-word` with
`wrap-anywhere` on the body text (open and collapsed states) and the
title. `overflow-wrap: anywhere` reduces min-content width, which
`break-word` does not, letting long tokens wrap inside the cards rather
than inflating the track.

The `line-clamp-2` collapsed preview is preserved unchanged.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-08-08 12:59:41 -04:00
Will PflegerandGitHub 6e5c462ac5 chore(release): release Buzz Relay version 0.2.1 (#2856)
## Buzz Relay release v0.2.1

### Changes since relay-v0.2.0:

- fix(sdk): preserve self-mention p tags in message and forum event
builders ([#4975](https://github.com/block/buzz/pull/4975))
([`78c87ae20e`](https://github.com/block/buzz/commit/78c87ae20e182fffdd99744d6c9ff99df82b159c))
- feat(desktop): adding rich link previews to messages
([#3818](https://github.com/block/buzz/pull/3818))
([`1922d49cb2`](https://github.com/block/buzz/commit/1922d49cb200a3382a91ec253f530b44dfda5f55))
- feat(relay): accept kind:30179 private managed-agent events at ingest
([#5133](https://github.com/block/buzz/pull/5133))
([`ad923353a2`](https://github.com/block/buzz/commit/ad923353a24b784df13a7c88757d6b24ebe36299))
- fix(media): require authenticated reads
([#4610](https://github.com/block/buzz/pull/4610))
([`769ac70b74`](https://github.com/block/buzz/commit/769ac70b741e3ad6809bff14eba29d3dd2cbd318))
- feat(identity): recover desktop identity from a signed-in phone
([#4845](https://github.com/block/buzz/pull/4845))
([`6eb65919f1`](https://github.com/block/buzz/commit/6eb65919f1eabd46b3850c15eefab31092dd500b))
- ci: prove the relay-driven mesh lifecycle — discover, join, infer,
deny — with real nodes
([#3862](https://github.com/block/buzz/pull/3862))
([`38bf642fcf`](https://github.com/block/buzz/commit/38bf642fcfa7a9fc1e06d6cf87d66ae94da29341))
- relay: fuzz WebSocket 1012 restart-close timing on graceful drain
(BUZZ_DRAIN_JITTER_MS)
([#4542](https://github.com/block/buzz/pull/4542))
([`e14fff74d0`](https://github.com/block/buzz/commit/e14fff74d00623acd30945eec5be366e25b0cf09))
- fix(reactions): support max-length custom emoji
([#3833](https://github.com/block/buzz/pull/3833))
([`2ea9385015`](https://github.com/block/buzz/commit/2ea9385015fb922de2adf0a53e86fc5a21d07b90))
- fix(channels): restrict private-channel invitations
([#4612](https://github.com/block/buzz/pull/4612))
([`efe1893dd3`](https://github.com/block/buzz/commit/efe1893dd372cfb92ed2e8a3ada2ed7b62c9477a))
- fix(workflow): bind trigger author to the signed event
([#4607](https://github.com/block/buzz/pull/4607))
([`885bed35ee`](https://github.com/block/buzz/commit/885bed35eee3f933c48d333c8979fdbc038e98b9))
- fix(git): revoke access for banned relay members
([#4608](https://github.com/block/buzz/pull/4608))
([`997b8caaa4`](https://github.com/block/buzz/commit/997b8caaa4c9e5af69dd8a496b4995d09a69f694))
- Define private managed agent wire protocol
([#4593](https://github.com/block/buzz/pull/4593))
([`067c085f37`](https://github.com/block/buzz/commit/067c085f37d9dcb2f598b0e2a6b6653903364783))
- perf(relay): index channel-id lookups and skip trace-only reads
([#4647](https://github.com/block/buzz/pull/4647))
([`bc9e6528a7`](https://github.com/block/buzz/commit/bc9e6528a7ba6007c5a25f6a0aca9c05d72e9d2c))
- Polish mobile inbox and media flows
([#4512](https://github.com/block/buzz/pull/4512))
([`feccf4eabc`](https://github.com/block/buzz/commit/feccf4eabc23fdba94ce3537a194357ed17b197c))
- fix(git): allow deleting the default branch
([#4297](https://github.com/block/buzz/pull/4297))
([`fc598f5f8d`](https://github.com/block/buzz/commit/fc598f5f8d70728d11d0712b9fa8e3acc44ea4c3))
- feat(projects): add buzz projects CLI commands (NIP-MP kind:30621)
([#4020](https://github.com/block/buzz/pull/4020))
([`b7bb15122e`](https://github.com/block/buzz/commit/b7bb15122e8a2053b545dc2210afc167f6c7a626))
- perf(relay): serve relay-membership checks from the read replica
([#4124](https://github.com/block/buzz/pull/4124))
([`ac4fa13b8e`](https://github.com/block/buzz/commit/ac4fa13b8e4d947071d57deb6918dcf12bf74961))
- fix(relay): allow open relays to set their NIP-11 workspace icon
(kind:9033) ([#3998](https://github.com/block/buzz/pull/3998))
([`5765fc74b7`](https://github.com/block/buzz/commit/5765fc74b77224f0207ddd4b41736a5ff18d333d))
- feat(relay): accept kind:30621 multi-repo projects at ingest
([#3171](https://github.com/block/buzz/pull/3171))
([`cb9701cd30`](https://github.com/block/buzz/commit/cb9701cd30fb344bf134585634a09007f3155bfb))
- feat(relay): raise hosted community limit to five
([#3829](https://github.com/block/buzz/pull/3829))
([`10d5a26414`](https://github.com/block/buzz/commit/10d5a26414dc90dc89fd27de74b21e105d4fa622))
- fix(relay): align NIP-11 max_limit with REQ ceiling
([#3635](https://github.com/block/buzz/pull/3635))
([`23f0c26b1c`](https://github.com/block/buzz/commit/23f0c26b1ceba8e07bf3c160a1e08c7bda82ccd9))
- feat(relay): gate kind 30178 team-catalog reads behind the shared tag
([#3358](https://github.com/block/buzz/pull/3358))
([`114d40d9d3`](https://github.com/block/buzz/commit/114d40d9d37f05eff83ee90347ed93fb3da512c5))
- fix(db): isolate usage metrics advisory-lock test on scratch DB
([#3670](https://github.com/block/buzz/pull/3670))
([`dba97eecd9`](https://github.com/block/buzz/commit/dba97eecd9d8659c9c816cd6666fa6d687b6bca1))
- perf(presence): reduce heartbeat frequency
([#3783](https://github.com/block/buzz/pull/3783))
([`bf139e8d0b`](https://github.com/block/buzz/commit/bf139e8d0bdba10df9a5adbf16843140e0a78a59))
- feat(mesh): upgrade embedded mesh to v0.74 and harden shared compute
(split 1/2 of #3467) ([#3741](https://github.com/block/buzz/pull/3741))
([`4933672eb4`](https://github.com/block/buzz/commit/4933672eb4589e7208b312829ebddcd10dfa9dd3))
- feat(replica): portable heartbeat-token fence with snapshot-local
reader routing ([#3268](https://github.com/block/buzz/pull/3268))
([`63496cc1d4`](https://github.com/block/buzz/commit/63496cc1d4c6f1b7c613801bdcc694169dcf391a))
- fix(git): channel binding tooling + author remediation for unbound
repos ([#3626](https://github.com/block/buzz/pull/3626))
([`788b3c002b`](https://github.com/block/buzz/commit/788b3c002bd2509455444f57f8a03a054b4b496a))
- feat: configure S3 URL addressing style
([#3400](https://github.com/block/buzz/pull/3400))
([`7012d86d52`](https://github.com/block/buzz/commit/7012d86d52fd188b27c7beedeaa132d9c1f61fa8))
- feat(tracing): correlate trace IDs in relay logs
([#3608](https://github.com/block/buzz/pull/3608))
([`005b5b819a`](https://github.com/block/buzz/commit/005b5b819a98ce85d4d80cd81b258fb6f9b8d51e))
- fix(relay): avoid subscription lock inversion
([#3413](https://github.com/block/buzz/pull/3413))
([`22be8bb351`](https://github.com/block/buzz/commit/22be8bb35177e27efc2dca2534df9a8dd871eae0))
- feat(cli): add users set-status command for NIP-38 profile status
([#3253](https://github.com/block/buzz/pull/3253))
([`60158fce3e`](https://github.com/block/buzz/commit/60158fce3e670f11bb35d42627857ccaea50ff06))
- feat(relay): make Postgres pool size configurable, default 50
([#3191](https://github.com/block/buzz/pull/3191))
([`2ce2d71cc3`](https://github.com/block/buzz/commit/2ce2d71cc38a9657eaf344c10e07f155b8a18615))
- feat(tracing): add datastore tracing plumbing
([#2760](https://github.com/block/buzz/pull/2760))
([`e94b9aeda0`](https://github.com/block/buzz/commit/e94b9aeda0b2272d36e3744e78680be69295b8b5))
- feat(invites): add use-limited invite links
([#3141](https://github.com/block/buzz/pull/3141))
([`d500c2d5cf`](https://github.com/block/buzz/commit/d500c2d5cf5d9aabe0ca4ebebfcafdbe5f5b7fd3))
- feat(admin): show reported message content in report detail
([#3149](https://github.com/block/buzz/pull/3149))
([`f069a85503`](https://github.com/block/buzz/commit/f069a8550373328babe4239ed614fcdf884721e2))
- resolve findings ([#3150](https://github.com/block/buzz/pull/3150))
([`9b0f744804`](https://github.com/block/buzz/commit/9b0f744804697b802f7afb88947194702765c78d))
- Revert "fix(cli,relay): resolve agents by verified owner"
([#3168](https://github.com/block/buzz/pull/3168))
([`a041e2d21e`](https://github.com/block/buzz/commit/a041e2d21e292a271fdfc26f0cdcdd0456f815c5))
- fix(cli,relay): resolve agents by verified owner
([#2615](https://github.com/block/buzz/pull/2615))
([`c3084b36d9`](https://github.com/block/buzz/commit/c3084b36d975259f2dfeee8edc9131b40a8bce83))
- fix(security): enforce durable community ban on NIP-43 relay-admin
kinds 9030-9033 ([#3128](https://github.com/block/buzz/pull/3128))
([`e2e0079101`](https://github.com/block/buzz/commit/e2e007910114ddf7c5a4e93bb03f6afe13552e92))
- fix(security): authorize kind:9000 role changes in both directions
([#3017](https://github.com/block/buzz/pull/3017))
([`00ecf2cac7`](https://github.com/block/buzz/commit/00ecf2cac7544d986b4eb111ad0a8b1d7560791f))
- feat(desktop): handle project work from Inbox
([#3117](https://github.com/block/buzz/pull/3117))
([`c5c4f390b6`](https://github.com/block/buzz/commit/c5c4f390b6713256e2efb8394c59823ebad73db6))
- feat(relay): make per-owner community limit configurable via
BUZZ_MAX_COMMUNITIES_PER_OWNER
([#2599](https://github.com/block/buzz/pull/2599))
([`2a051a404d`](https://github.com/block/buzz/commit/2a051a404dcde42dddbff2a0b33f717ffe9cf999))
- feat(relay): add author-only-unless-shared read gate for kind 30175
([#2768](https://github.com/block/buzz/pull/2768))
([`ab3af82871`](https://github.com/block/buzz/commit/ab3af828714ab699dfc87644d234014987a4fe6b))
- fix(core): block IPv6 transition SSRF targets
([#2801](https://github.com/block/buzz/pull/2801))
([`c26bf5945d`](https://github.com/block/buzz/commit/c26bf5945d8f2ef19746a78e80a7c1dae2ef3db9))
- fix(workflow): bypass system proxies for webhooks
([#2800](https://github.com/block/buzz/pull/2800))
([`60a171b19e`](https://github.com/block/buzz/commit/60a171b19efd515d9213b535d52a2bcbec3ff2fe))
- fix(audit): hash created_at at the precision Postgres stores
([#2638](https://github.com/block/buzz/pull/2638))
([`264a56a226`](https://github.com/block/buzz/commit/264a56a2260ac87350bfe1f5d3ec3d89615eb47c))
- feat(desktop): make pull request reviews actionable
([#2510](https://github.com/block/buzz/pull/2510))
([`9081ab0ec9`](https://github.com/block/buzz/commit/9081ab0ec9c5d91548c7f5ff52eba6cca4788dd0))
- fix(relay): decompress gzip-encoded git smart-HTTP request bodies
([#2670](https://github.com/block/buzz/pull/2670))
([`5ca36e7b91`](https://github.com/block/buzz/commit/5ca36e7b919097733868764d8e0073e99c3206c3))
- fix(sharing): preserve agent/team snapshot tEXt chunks through media
sanitization ([#2438](https://github.com/block/buzz/pull/2438))
([`b096b0a15a`](https://github.com/block/buzz/commit/b096b0a15af4c4566365c5b1efe7f39b700222ed))
- fix(relay): send 1012 restart close to all clients on graceful drain
([#2575](https://github.com/block/buzz/pull/2575))
([`1911c69aa2`](https://github.com/block/buzz/commit/1911c69aa2912c1408bd6b21759b657458fb43af))
- fix(media): sanitize animated image uploads
([#2524](https://github.com/block/buzz/pull/2524))
([`8f8f5fa5a4`](https://github.com/block/buzz/commit/8f8f5fa5a4b2463cdc6c2a527acb7086150cdaae))
- fix(channels): strip leading hash prefixes from names
([#2250](https://github.com/block/buzz/pull/2250))
([`d0ab3fdb05`](https://github.com/block/buzz/commit/d0ab3fdb054e0cfedbf21e4c5143ad6c671c10cc))
- feat(relay): make Redis pool size configurable, default 16
([#2521](https://github.com/block/buzz/pull/2521))
([`bcc3e13069`](https://github.com/block/buzz/commit/bcc3e1306946528102bb26be9a7c41299e2f8e00))
- feat(desktop+acp): spawn a harness per (agent, community) pair at GUI
startup — warm sockets, lazy LLM pool
([#2122](https://github.com/block/buzz/pull/2122))
([`61cc738ee8`](https://github.com/block/buzz/commit/61cc738ee8991e92563136de4b77e54cb9756420))
- feat(media): add S3-truth per-community storage sweep
([#2044](https://github.com/block/buzz/pull/2044))
([`bd37a4d584`](https://github.com/block/buzz/commit/bd37a4d584fefc1d13ad8abadf6e890e66183072))
- feat(relay): log NIP-98 pubkey attribution on HTTP bridge requests
([#2206](https://github.com/block/buzz/pull/2206))
([`7e34bee62c`](https://github.com/block/buzz/commit/7e34bee62cacaa9d8a96c14d5892a471b59a1983))
- Revert "feat(relay): inventory unreachable Git objects"
([#2275](https://github.com/block/buzz/pull/2275))
([`0fb820f9bf`](https://github.com/block/buzz/commit/0fb820f9bfbd7e19e48f9826e332920c2ee2c229))
- feat(relay): inventory unreachable Git objects
([#2264](https://github.com/block/buzz/pull/2264))
([`3afc9dae15`](https://github.com/block/buzz/commit/3afc9dae159262220c4149e9c8add50772869318))
- relay: add author_type label to buzz_events_stored_total
([#2243](https://github.com/block/buzz/pull/2243))
([`b9f54c43fe`](https://github.com/block/buzz/commit/b9f54c43fe2bcd0eb8fb3b76914e9aa0c31f6927))
- fix(git): make project branch workflows reliable
([#2213](https://github.com/block/buzz/pull/2213))
([`166f27be4b`](https://github.com/block/buzz/commit/166f27be4bc1abf2d465493bf2137353045399dc))
- feat(cli): manage repository protection rules
([#2193](https://github.com/block/buzz/pull/2193))
([`f94324598d`](https://github.com/block/buzz/commit/f94324598d84b2db9a05a3fa1f855970c4c5b575))
- feat(cli): add agents archive/unarchive/archived subcommands
([#2173](https://github.com/block/buzz/pull/2173))
([`7d7992067b`](https://github.com/block/buzz/commit/7d7992067b2914b582b7e6d31a6174603b480b4b))
- fix(mobile): sanitize Android image uploads
([#2188](https://github.com/block/buzz/pull/2188))
([`ee21da90bd`](https://github.com/block/buzz/commit/ee21da90bd6b1da6bfaaf22ba00749398aaa9640))
- fix(cli): paginate channel directory queries
([#2181](https://github.com/block/buzz/pull/2181))
([`03fe19d603`](https://github.com/block/buzz/commit/03fe19d6033094ae2ec4c89c26eb23174ef53daa))
- fix(mobile): image upload fails due to unstripped metadata
([#2185](https://github.com/block/buzz/pull/2185))
([`37f15b2001`](https://github.com/block/buzz/commit/37f15b20019169363b697aee41c99573b7bc3f24))
- perf(relay): compact Git packs before manifest limits
([#2172](https://github.com/block/buzz/pull/2172))
([`80e0ab16b0`](https://github.com/block/buzz/commit/80e0ab16b03c656ec8def18bedc27eaf29c02867))
- perf(relay): cache Git pack hydration
([#2169](https://github.com/block/buzz/pull/2169))
([`a4d82ec722`](https://github.com/block/buzz/commit/a4d82ec7226e685a933dbd829b6bb8bce0787b4e))
- fix(relay): bound and observe Git read operations
([#2167](https://github.com/block/buzz/pull/2167))
([`5f7c93d9c1`](https://github.com/block/buzz/commit/5f7c93d9c12ce7894288ae47f3fe223fcff2dce3))
- relay: gate push enqueue on live leases; batch matcher pipeline
(T1b/T1a-repair/T2b) ([#2145](https://github.com/block/buzz/pull/2145))
([`e43b2d5aac`](https://github.com/block/buzz/commit/e43b2d5aac0d1f2b6b623b04f7af5a51f77da8c6))
- relay: add audit logging disable switch
([#2134](https://github.com/block/buzz/pull/2134))
([`bf5acabdde`](https://github.com/block/buzz/commit/bf5acabdde44aa133bdcbafcf9e1a4ff752c3302))
- relay: skip TTL deadline bump for known-permanent channels (T1a
write-amp) ([#2125](https://github.com/block/buzz/pull/2125))
([`2e936d439c`](https://github.com/block/buzz/commit/2e936d439ce29182b48086f9f8a7a3ffe3b9b345))
- fix(git): carry NIP-OA delegation in auth event
([#2120](https://github.com/block/buzz/pull/2120))
([`c12257d57a`](https://github.com/block/buzz/commit/c12257d57a54d5c1e16435440b02beb5d1c057b8))
- Route lag-tolerant reads to an optional Postgres read replica
([#2084](https://github.com/block/buzz/pull/2084))
([`29c48883d3`](https://github.com/block/buzz/commit/29c48883d30e6feed75e33490571ca96082c6282))
- fix: recover community access visibility
([#2074](https://github.com/block/buzz/pull/2074))
([`ca384d082d`](https://github.com/block/buzz/commit/ca384d082d9804ec53a3fd12ccbf4a0846b21d92))
- feat: proxy feedback-scoped admin attachments
([#2059](https://github.com/block/buzz/pull/2059))
([`d7f918e3cb`](https://github.com/block/buzz/commit/d7f918e3cbcc4d30f222d0f4ae836de808f859a2))
- feat: add read-only deployment moderation dashboard
([#1999](https://github.com/block/buzz/pull/1999))
([`68e670e001`](https://github.com/block/buzz/commit/68e670e001d2bed2cf141095926feaf482c3bed8))
- Bug-bash round 2: table scroll, Goose instructions, workflow mention
wake ([#2034](https://github.com/block/buzz/pull/2034))
([`64b8fea6dc`](https://github.com/block/buzz/commit/64b8fea6dce3be684aa0bac5dbd701e46dc7e432))
- Strip media metadata on clients and reject it at the relay
([#2006](https://github.com/block/buzz/pull/2006))
([`5cfd69cb0c`](https://github.com/block/buzz/commit/5cfd69cb0cf1dc63d718454defe3b8a8aaf5f15b))
- [codex] Hold Git concurrency permits through streaming (BUZZ-SEC-018)
([#1916](https://github.com/block/buzz/pull/1916))
([`7baea42abb`](https://github.com/block/buzz/commit/7baea42abbbb794e6e5ab0e9df11e2d1b0550d0b))
- [codex] Enforce shared relay admission limits (BUZZ-SEC-019)
([#1917](https://github.com/block/buzz/pull/1917))
([`73fc0ec6cf`](https://github.com/block/buzz/commit/73fc0ec6cf58a79bfc65e42faba457bf49c2d232))
- [codex] Block banned actors from moderation commands (BUZZ-SEC-007)
([#1915](https://github.com/block/buzz/pull/1915))
([`caa195ca58`](https://github.com/block/buzz/commit/caa195ca58ea49cf8ed9c3ede55d6a2e4ed37096))
- [codex] Fix relay WebSocket admission limits
([#1682](https://github.com/block/buzz/pull/1682))
([`d3ce971fc7`](https://github.com/block/buzz/commit/d3ce971fc75a34162d5498c27ac4a1c30236630a))
- feat: add invite QR and mobile direct join
([#1957](https://github.com/block/buzz/pull/1957))
([`648cbf3610`](https://github.com/block/buzz/commit/648cbf36109d97be6bd8530e77073d1c7e6008a0))
- fix(join-policy): require legal consent on hosted invites
([#1987](https://github.com/block/buzz/pull/1987))
([`2e1577f76f`](https://github.com/block/buzz/commit/2e1577f76f5105ddacda7be884518574ca8d6b96))
- [codex] Prevent actor-tag UI impersonation
([#1931](https://github.com/block/buzz/pull/1931))
([`c540ec9678`](https://github.com/block/buzz/commit/c540ec967869ef0f4eef90439bf70929fc74f7f6))
- Scope relay runtime state by community
([#1658](https://github.com/block/buzz/pull/1658))
([`d52dedb06f`](https://github.com/block/buzz/commit/d52dedb06fc2c7692c6d9225c7a08b41a509633a))
- Apply optional relay join policy across join flows
([#1894](https://github.com/block/buzz/pull/1894))
([`6c2d667575`](https://github.com/block/buzz/commit/6c2d667575cbc372ba42d26134448660fb1d2ee9))
- feat(media): require auth for relay media reads
([#1926](https://github.com/block/buzz/pull/1926))
([`f308762852`](https://github.com/block/buzz/commit/f3087628524951de91028c9d263bcd0d0a727fab))
- feat(relay): add community unarchive endpoint
([#1908](https://github.com/block/buzz/pull/1908))
([`6b9641db2b`](https://github.com/block/buzz/commit/6b9641db2b4709b71622b7ef0b799117a0605ca6))
- feat(relay): gate Git web GUI separately
([#1901](https://github.com/block/buzz/pull/1901))
([`34dc7dec75`](https://github.com/block/buzz/commit/34dc7dec75285ab6f2107ce5cad170b80b92206a))
- mesh: upgrade runtime, enforce membership, add shared compute provider
([#1656](https://github.com/block/buzz/pull/1656))
([`54638ff4bb`](https://github.com/block/buzz/commit/54638ff4bb5af2d3d3759b44118b43052f814bb1))
- Route Git scratch through configured volume
([#1884](https://github.com/block/buzz/pull/1884))
([`2318b3096c`](https://github.com/block/buzz/commit/2318b3096c585f8d31bd43e27dc5d6305c5fe20d))
- feat(relay): gate usage metrics behind stable leader
([#1814](https://github.com/block/buzz/pull/1814))
([`59e9821503`](https://github.com/block/buzz/commit/59e9821503a2fe23fc4630aa0f36bb252ae4566f))
- Relay mesh: cross-pod tunnel + huddle transport (buzz-relay-mesh)
([#1670](https://github.com/block/buzz/pull/1670))
([`ccb021d713`](https://github.com/block/buzz/commit/ccb021d71339009aabedc383c8f3d8e5c23e1e42))
- feat(push): deliver accepted relay events as wakes
([#1866](https://github.com/block/buzz/pull/1866))
([`bffbc5f22c`](https://github.com/block/buzz/commit/bffbc5f22cc80e9a07dedc622798347d598a215c))
- fix(db): resolve duplicate migration version
([#1863](https://github.com/block/buzz/pull/1863))
([`08ad38a07f`](https://github.com/block/buzz/commit/08ad38a07f0c49bb3f20b775b8f534f1cfa529c3))
- Add private product feedback sidecar
([#1857](https://github.com/block/buzz/pull/1857))
([`af190c93e1`](https://github.com/block/buzz/commit/af190c93e1048af64c3fbfb3831c689cb703997c))
- feat(relay): add durable community archival
([#1834](https://github.com/block/buzz/pull/1834))
([`2b15a72675`](https://github.com/block/buzz/commit/2b15a726750dbd7437711050cd3241b679dff317))
- feat(push): add public APNs gateway
([#1770](https://github.com/block/buzz/pull/1770))
([`1c006822e4`](https://github.com/block/buzz/commit/1c006822e4484d68e33fce14f9139c2f70ce9d66))
- feat(relay): add atomic community ownership transfer
([#1845](https://github.com/block/buzz/pull/1845))
([`52e42ccb9f`](https://github.com/block/buzz/commit/52e42ccb9fc85445814614c72d40f346e986152b))
- Bound NIP-RS retention and search indexing
([#1771](https://github.com/block/buzz/pull/1771))
([`1b4703021d`](https://github.com/block/buzz/commit/1b4703021dbfd37dc31845223dba9ba182e4647f))
- Add optional standalone pairing relay to Helm chart
([#1799](https://github.com/block/buzz/pull/1799))
([`9b47c8548f`](https://github.com/block/buzz/commit/9b47c8548fd061fbb806ea8b9ddee831c19cf80e))
- fix(relay): publish membership snapshot on provisioning
([#1761](https://github.com/block/buzz/pull/1761))
([`0950d392b7`](https://github.com/block/buzz/commit/0950d392b7a862694c95cbea1cec45985ee42996))
- feat(relay): per-community usage metrics
([#1723](https://github.com/block/buzz/pull/1723))
([`620822899a`](https://github.com/block/buzz/commit/620822899a6373fa3a17a87815cd7cade25ed332))
- refactor(desktop): remove vestigial MCP toolsets config
([#1776](https://github.com/block/buzz/pull/1776))
([`dfec75b3c0`](https://github.com/block/buzz/commit/dfec75b3c0b8080529e4d9089d4ed80e3902aaed))

**To release:** merge this PR. The tag and build will happen
automatically.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
relay-v0.2.1
2026-08-08 12:26:24 -04:00
c815a9c6e1 chore(release): release Buzz Desktop version 0.5.8 (#5326)
## Buzz Desktop release v0.5.8

- **Frozen main:** `6a17d035f79ad582ca3f4f3cdc38d376f2c4087f`
- **Reviewed candidate:** `f3de860574bb3119018b4592353e9761635aeb07`
- **Previous desktop release:** `desktop-v0.5.7`
- **Proposed immutable tag:** `desktop-v0.5.8`

This PR may be **squash merged** after the Desktop Release Candidate
check and all protected-branch checks pass. Merging authorizes
publication of the exact reviewed candidate; later or unrelated changes
on `main` cannot alter it.

The checked-in changelog accounts for every non-merge commit in the
release range. The Desktop tag points to the reviewed candidate commit,
not the later squash commit. Publication remains bound to that immutable
candidate tag.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
2026-08-08 09:18:29 -07:00
261c460761 fix(buzz-agent): recover from 400-shaped image rejections; unbound benchmark agent rounds (#5318)
## Problem

Two failure modes from the `tb21-glm52-crusoe-1` benchmark run (GLM-5.2
solo, TB2.1) wedged or killed 13 of 89 trials without the model being at
fault:

1. **Conversation poisoning on text-only endpoints.** Crusoe's
serverless `crusoeai/GLM-5.2-NVFP4` rejects any request whose history
contains an image with `400: ... is not a multimodal model`. The
recovery machinery for exactly this case already exists —
`AgentError::UnsupportedImageInput` → `replace_unsupported_images()`
strips the image blocks, marks the tool result as an error, substitutes
a text placeholder, and continues the turn. But classification only
matched OpenRouter's 404 body (`no endpoints found that support image
input`) and was only consulted on the 404 arms. The Crusoe 400 fell
through to terminal `AgentError::Llm`: the image stayed in history,
every subsequent call failed identically, buzz-acp rode its 10-retry
ladder (~40 min), and the trial idled to budget death. Measured blast
radius: **8 trials wedged, 12.7h aggregate idle-after-poison.**

2. **Bounded agent rounds in benchmark trials.** The harness default
`DEFAULT_MAX_AGENT_ROUNDS = 32` ended solo trials mid-work when turns
rotated (thinking-heavy models hit max_tokens rotation fast; 4 trials
died this way). Benchmark trials already have a wall-clock budget as the
real limit — the round cap only converts recoverable rotation into trial
death.

## Fix

- `is_unsupported_image_input_error()` also matches the verbatim `is not
a multimodal model` body. Matcher stays deliberately tight (same
doctrine as `is_context_length_error`): misclassifying a generic 400 as
recoverable would mutate history for an error that removing images
cannot fix.
- Both status ladders — shared `post()` and `openrouter_post()` —
consult it on their 400 arms and return the typed
`UnsupportedImageInput` (OpenAI-compatible providers report this as 400;
a BYOK/passthrough upstream can surface the provider's own 400 through
OpenRouter).
- Harness `DEFAULT_MAX_AGENT_ROUNDS` → `0` (unbounded —
`BUZZ_AGENT_MAX_ROUNDS=0` is the agent config's documented unbounded
value). Per-agent `budget.max_calls` in manifests still overrides.

## Acceptance

- A 400 with the image-rejection body reaches the existing image-strip
recovery path instead of wedging the session — asserted through
`complete()` (covers the return path into the convergence mapper) and at
the `openrouter_post` terminal, both proving single-attempt (a
deterministic capability rejection must never be retried).
- Ordinary 400s stay terminal `AgentError::Llm` (existing negative tests
unchanged).
- Benchmark trials run unbounded rounds by default; python tests updated
for 0-is-legal with a negative arm at -1.

## Verification

- `cargo test -p buzz-agent`: 427 + 18 + 20 + 15 + 8 + 1 + 48 passed, 0
failed (full package, 3 consecutive clean runs)
- `cargo clippy -p buzz-agent --all-targets`, `cargo fmt --check`: clean
- `uv run --extra dev pytest tests/` in harbor-buzz-orchestra: 35 passed
- Pre-push hooks (full workspace rust-tests + desktop-tauri-checks)
green on rustc 1.95.0 at head b0438602

Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
2026-08-08 12:17:22 -04:00