Commit Graph
1353 Commits
Author SHA1 Message Date
cca8839034 Make relay reconnect backoff authoritative (#3774)
## Summary

- make the relay reconnect coordinator authoritative during outages so
query, publish, and subscription traffic waits for the scheduled attempt
instead of cancelling backoff
- release waiting operations after the coordinated AUTH +
live-subscription replay attempt, while preserving one explicit manual
reconnect fast path
- suppress duplicate notification side effects when reconnect replay
overlaps previously delivered events

## Root cause

`resetConnection()` scheduled exponential backoff, but
`ensureConnected()` cleared any pending reconnect timer. Operation-level
retry paths immediately called `ensureConnected()`, so ordinary app
traffic could repeatedly bypass the reconnect policy during an outage.
The resulting churn also replayed overlapping live events into
notification side effects without a shared event-ID guard.

## Validation

- `pnpm --dir desktop typecheck`
- `pnpm --dir desktop test` — 3,823 passed
- pre-push: `desktop-check`, `desktop-test`, and `branch-skew` passed
- file-size, px-text, and pubkey-truncation ratchets passed

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-30 11:25:08 -07:00
bd0bff24bf feat(desktop): add password-protected backups in settings (#3701)
**Category:** new-feature
**User Impact:** Users can create, download, and verify a
password-protected backup of their private identity from desktop
Settings.

**Problem:** Buzz does not currently give signed-in users a
Settings-based path to protect or validate their private identity
independently of onboarding. **Solution:** Add a focused backup menu to
the private-key row, keep encryption and verification local in Rust, and
preserve completed encrypted backups briefly so native saves can be
retried without repeating encryption.

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

**desktop/src/features/settings/**
Adds the background backup lifecycle, create and test dialogs,
private-key menu integration, password handling, and focused unit
coverage.

**desktop/src/features/onboarding/ui/NsecMaskedDisplay.tsx**
Extends the masked private-key display with reusable overflow-menu
actions used by Settings.

**desktop/src/app/App.tsx**
Mounts the backup provider at app scope so encryption and save work
survive closing Settings or the modal.

**desktop/src/shared/api/tauriIdentity.ts**
Adds typed desktop bindings for local backup creation, save, selection,
and verification.

**desktop/src-tauri/src/key_backup.rs and
desktop/src-tauri/src/commands/identity.rs**
Implements local NIP-49 encryption, password generation, file handling,
and public-identity-only verification results.

**desktop/src-tauri/src/egress_guard.rs and guarded call sites**
Blocks encrypted secret material from relay, websocket, snapshot,
sharing, and huddle egress paths.

**desktop/src-tauri tests and fixtures**
Covers encryption, verification, file behavior, and fail-closed
no-egress protections.

**desktop/src/testing/e2eBridge.ts, desktop/tests/, and
desktop/playwright.config.ts**
Expands the mock native bridge and browser coverage across create,
retry, expiry, and current/different-identity verification states.

**desktop/src-tauri/Cargo.toml, Cargo.lock, and assets**
Adds the local cryptography/password-generation dependencies and
embedded short-word list.

</details>

## Reproduction steps

1. Run the desktop app and open **Settings → Profile → Identity**.
2. Open the private-key overflow menu and choose **Create backup**.
3. Enter or generate a valid password, submit, and confirm progress
continues if the dialog or Settings is closed.
4. Save the resulting `.ncryptsec` file; cancel and retry to confirm the
temporary download remains available.
5. Choose **Test backup**, select the file, enter a wrong password, then
retry with the correct password.
6. Confirm success identifies whether the backup matches the current
identity and displays only the public `npub`.

## Screenshots

| Settings identity | Private-key menu | Create backup |
|---|---|---|
| <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/981e391b-6829-4081-95ca-ca75a369de71"
/> | <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/7972c68e-7635-47d8-b0ad-9639390d3e6c"
/> | <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/4709c8f7-cf02-46f1-bec9-b3f98fe56fb2"
/> |

| Encrypting | Download available | Test success |
|---|---|---|
| <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/1ac3e934-2b4b-4135-bae6-126c715c8c59"
/> | <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/cb6f07ee-a16f-44a5-b9a0-6b9fe0e4d40d"
/> | <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/ea58b1b1-966c-46aa-8d59-92c9f06a25bd"
/> |

Visual review and additional states: [Buzz
thread](buzz://message?channel=50ca7ef1-201e-4159-9499-40de3964b7c3&id=87eceb5f0f82fd50c32e560de3d35be48e293760f6620718aafdcef289d475fe)

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
2026-07-30 11:15:39 -07:00
f44b5a2477 fix(desktop): reuse profiles when joining communities (#2155)
## Why
People joining a community with an existing relay profile should not be
asked to recreate their name and avatar.

## What
- Check the active identity's relay profile after the joined community
becomes active
- Skip directly to the starter-team step when a kind-0 profile event
exists
- Preserve the profile setup path when no event exists or discovery
fails
- Cover both new-profile and existing-profile join paths in E2E tests

## Risk Assessment
Low — the lookup is scoped to the community onboarding profile stage,
runs once per transaction, and fails open to the existing flow.

## References
- `pnpm build:e2e && pnpm exec playwright test --project=integration
tests/e2e/onboarding.spec.ts --grep 'first-community direct join reaches
profile|community onboarding reuses an existing relay profile'` (2
passed)

Generated with Codex

Signed-off-by: npub1rf6fvdj6ut0c4kcmjv4p5mmgh89nj58n69uu3fz3cvk3jn500hqs7emz79 <1a7496365ae2df8adb1b932a1a6f68b9cb3950f3d179c8a451c32d194e8f7dc1@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1rf6fvdj6ut0c4kcmjv4p5mmgh89nj58n69uu3fz3cvk3jn500hqs7emz79 <1a7496365ae2df8adb1b932a1a6f68b9cb3950f3d179c8a451c32d194e8f7dc1@sprout-oss.stage.blox.sqprod.co>
2026-07-30 10:48:54 -07:00
61b96c9828 fix(catalog): update Amp description (#3758)
## Summary
- replace Amp's outdated Sourcegraph attribution in the runtime catalog
- describe Amp neutrally as a coding agent for the terminal and editor

## Verification
- `pnpm test` (desktop: 3,819 passed)
- `pnpm typecheck`
- pre-push `desktop-check`, `desktop-test`, and `branch-skew` hooks

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-30 12:54:13 -04:00
02be413b82 feat(catalog): resolve publisher display name in catalog detail pane (#3640)
The catalog detail pane hardcoded "Community member" for every non-own
catalog entry. The publisher pubkey (`catalogSource.ownerPubkey`) was
already on every entry — it just was not being resolved to a name.

## What changed

**`desktop/src/features/agents/ui/PersonaCatalogDialog.tsx`**

`PersonaCatalogDetail` now calls `useUsersBatchQuery([ownerPubkey])`
when the selected entry is a community (non-own) catalog agent. The
label derivation is extracted into the exported pure function
`resolveCatalogOwnerLabel` and uses truthy fallbacks to handle empty or
whitespace-only kind:0 fields:

- Own entry → `"You"` (unchanged)
- `displayName` present and non-blank → the display name
- `displayName` absent/blank but `name` present and non-blank → the name
- Loading, unresolvable, or both candidates blank → `"Community member"`
(fallback preserved)

The batch query is disabled (`enabled: false`) when the entry is not a
community entry, so there is no extra network call for own entries or
built-in agents.

**`desktop/src/features/agents/ui/personaCatalogOwnerLabel.test.mjs`**

Unit tests for `resolveCatalogOwnerLabel` covering: populated
`displayName` wins; whitespace-only `displayName` falls through to
`name`; both candidates empty/whitespace/null/undefined all fall through
to `"Community member"`.

**`desktop/tests/e2e/agents.spec.ts`**

- Updated the existing assertion — it previously checked for the
hardcoded fallback; now asserts the resolved mock display name
`"alice"`.
- Added "catalog detail shows Community member when the publisher
profile cannot be resolved" — installs a catalog event from an unknown
pubkey and asserts the fallback still renders.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1g8493u0xfsjrvflg4n08ezd7vec99mnwzlv0qgwpr9d7gvjwhuzqx59rhw <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz>
2026-07-30 11:38:28 -04:00
4933672eb4 feat(mesh): upgrade embedded mesh to v0.74 and harden shared compute (split 1/2 of #3467) (#3741)
## Summary

This is **part 1 of 2** split out from #3467 (per Tyler's request),
carrying only the mesh-scoped changes. The agent/ACP response-behavior
changes and the new `send_message` tool stay in #3467 as part 2. All
commits are @michaelneale's work, cherry-picked with authorship
preserved.

- Upgrade embedded Mesh to v0.74.0 (tag-pinned instead of commit rev)
and use canonical Gemma model IDs.
- Keep shared compute serving through member joins, roster changes, app
recovery, and community switching.
- Wait for actual model readiness and avoid resuming incomplete
downloads after quit.
- Leave `BUZZ_AGENT_THINKING_EFFORT` unset by default so each model's
chat template picks its own thinking default (`none` suppressed Gemma
tool-calling entirely; pinning `low` made Qwen3 burn ~4x output budget).
Explicit agent/persona/global values still win.

## Relationship to #3467

Contains the mesh commits from #3467 (`2cd640b23`, `0ad81c341`,
`ad13ed841`) rebased onto current main, with one deliberate exclusion:
the `crates/buzz-agent/src/llm.rs` reasoning→text parser change from
`2cd640b23` is **not** here. That change unconditionally affects every
OpenAI-compat/Responses provider, so it belongs with the reply-behavior
work in part 2, where it can be reviewed as what it is.

Not included (remaining in #3467 / part 2):
- typed `send_message` tool in dev-mcp + `BUZZ_ACP_SEND_MESSAGE_TOOL`
gating
- plain-reply delivery fallback in buzz-acp
(`BUZZ_ACP_DELIVER_PLAIN_REPLIES`)
- the mesh_agent_e2e P5/P6 rewrite (exists to prove the reply path)
- the two `env.insert` preset opt-ins in `relay_mesh.rs` for the flags
above
- the llm.rs parser change

This PR is independently mergeable; part 2's flags are all off by
default so it can land before or after.

## Testing

- `cargo test -p buzz-relay --locked` — 780 passed (one telemetry test
is order-sensitive under parallel default settings; passes in the
pre-push suite and standalone, unrelated to this diff — files untouched
here).
- `just desktop-tauri-test` (default features) — 1877 passed.
- `cargo test --locked --features mesh-llm` in `desktop/src-tauri` —
1961 passed, including the new relay-mesh preset and
coordinator/recovery tests.
- Both `Cargo.lock`s resolve with `--locked` against the v0.74.0 tag.
- Full pre-push hook suite green (rust-tests, desktop-check/test, tauri
checks).

Live validation of the mesh v0.74 upgrade itself is documented on #3467
(two-Mac cross-version test).

---------

Signed-off-by: Michael Neale <michael.neale@gmail.com>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: Michael Neale <michael.neale@gmail.com>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
2026-07-30 11:37:27 -04:00
9a386a0def Refine agent sharing dialog (#3699)
## Summary

- Refine the agent share dialog around recipient sharing, link copying,
catalog sharing, and export.
- Show memory settings only when a linked agent has memories to include.
- Use a catalog toggle for custom agents and keep built-in agents out of
the catalog flow.

## Validation

- `pnpm typecheck`
- Focused Playwright share and catalog flows

---------

Signed-off-by: kenny lopez <klopez4212@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-07-30 15:23:16 +00:00
Matthew BeckleyandGitHub c9aa55505c desktop: enable getUserMedia in the Linux WebKitGTK webview (#3607)
Microphone/camera capture works on macOS (WKWebView) and Windows
(WebView2) but fails on Linux with `NotAllowedError`. WebKitGTK ships
with `enable-media-stream` off and a default `permission-request`
handler that denies every request.

This reaches the underlying `webkit2gtk::WebView` from
`on_webview_ready` and enables `enable-media-stream`, then installs a
**deny-by-default** `permission-request` handler: a `UserMedia` request
is allowed only from a trusted app origin (`tauri://localhost` in prod,
the Vite dev origin in debug) **and** when it targets an audio/video
device — everything else is denied. No-op on macOS/Windows.

- `webkit2gtk` is pinned to the version wry already uses (`=2.0.2`) so
there's a single shared copy of the native binding.

---------

Signed-off-by: Beckley <mattcbeckley@gmail.com>
2026-07-30 11:19:42 -04:00
klopez4212andGitHub 73589408db fix: align responsive agent views (#3688)
## Summary
- Keep the Agents header full width while cards reflow independently.
- Collapse header actions into an overflow menu at the compact layout
threshold.
- Apply the same responsive grid rules to Agent Teams.

## Validation
- `pnpm -C desktop build:e2e`
- Focused Agents Playwright coverage
- Pre-push desktop checks and unit tests

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
2026-07-30 15:58:48 +01:00
d0a24bcb52 Add macOS agent menu-bar menu (#3565)
## Summary

- Add a **macOS-only** monochrome Buzz menu-bar icon with **Running**
and **Recent** agent sections.
- Show each agent as one selectable macOS row with `Name · elapsed` and
its channel underneath.
- Keep completed work available for quick channel re-entry, alongside
New Channel, Open Buzz, and Quit Buzz.
- Keep the main window alive on close and restore it from both the menu
bar and the macOS Dock.
- Fence queued channel actions by community generation so an in-flight
action from the previous community cannot navigate the newly selected
community.
- Leave Windows and Linux unchanged; cross-platform tray lifecycle
support can follow with platform-specific validation.

<img width="812" height="760" alt="Buzz macOS agent menu"
src="https://github.com/user-attachments/assets/19bfd874-8f06-4496-bdda-7ed2e7b5733f"
/>

## Validation

- `cargo fmt --check`
- Desktop Tauri suite: 1,860 passed, 14 ignored after merging current
`main`
- Desktop tests: 3,769 passed
- Desktop lint, file-size, text, and pubkey checks pass (two
pre-existing informational template-literal notices)
- Regression coverage verifies stale `OpenChannel` actions are discarded
across community changes while `NewChannel` survives

## Manual validation remaining

A native macOS smoke test is still requested before merge: menu
appearance, elapsed updates, Running → Recent, channel navigation, New
Channel, minimized/closed/Dock restore, Open Buzz, and Quit. E2E stubs
the tray IPC and does not exercise the native menu.

---------

Signed-off-by: kenny lopez <klopez4212@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-07-30 07:51:25 -07:00
klopez4212andGitHub 4672ee55c4 Fix pending message feedback (#3543)
## Summary

- Keep `Sending…` beside message timestamps, including grouped pending
messages.
- Match the profile-card hover surface to inactive channel rows.

## Snapshots

### Pending message

![Pending message
status](https://raw.githubusercontent.com/block/buzz/655189aec0904677a68651f8aabf0e17d69734cd/pr-3543--pending-message-inline.png)

### Profile hover

![Profile
hover](https://raw.githubusercontent.com/block/buzz/655189aec0904677a68651f8aabf0e17d69734cd/pr-3543--profile-hover.png)

## Validation

- `pnpm -C desktop typecheck`
- `pnpm -C desktop check:file-sizes`
- `pnpm -C desktop build:e2e`
- `pnpm -C desktop exec playwright test
tests/e2e/message-feedback-snapshots.spec.ts --project=smoke`

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
2026-07-30 07:46:56 -07:00
thomaspblockandGitHub c55e421a06 fix(desktop): remove remaining Projects panel fills (#3742)
## Summary

- let the Projects page surface flow through repository, pull request,
and issue list/grid layouts
- remove opaque fills from project-detail content across Overview,
Files, Commits, Issues, Pull Requests, and Contributors
- preserve borders, hover feedback, and intentional nested fills for
code, inputs, badges, and warnings

## Related

Follow-up to #3416.

## Testing

- `pnpm exec biome check` on the changed Projects UI and E2E files
- `pnpm build:e2e`
- focused Playwright smoke coverage for overview, subsection list/grid,
and project-detail transparency (3 passed)
- pre-commit checks passed
- desktop pre-push checks passed; the unrelated integration hook remains
blocked by a stale local checksum for migration 25

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
2026-07-30 15:31:10 +01:00
310df2ec33 desktop: restore direct community member adds (#3634)
## Summary

- restore direct community member adds in **Settings → Invites**
- keep one consolidated entry point: the dialog now supports both adding
someone directly and sharing an invite link
- accept npub or 64-character hex keys while preserving role hierarchy
(owners: Member/Admin; admins: Member only)
- verify an npub direct-add publishes the decoded hex key in a kind
`9030` NIP-IA event

## Why

The Invites consolidation left `AddMemberDialog` without a live mount
point, so the existing direct-add capability disappeared even though its
mutation path still existed. This reuses that implementation rather than
introducing a second one.

## Before and after

| Before | After |
| --- | --- |
| The consolidated dialog only offered a share link; there was no
direct-add path. | The same dialog now presents direct add and
share-link controls as one invitation flow. |
| ![Before: invite dialog with share-link controls
only](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3634/before-invite-dialog.png)
| ![After: polished community invite dialog with direct-add and
share-link
sections](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3634/invite-dialog-polished.png)
|

<details>
<summary>Before: Invites page entry point</summary>

![Before: Invites settings
page](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3634/before-invites-page.png)

</details>

## Verification

- `pnpm --dir desktop build:e2e`
- `pnpm exec playwright test
tests/e2e/invites-settings-screenshots.spec.ts --project=smoke` — 4
passed
- targeted Biome check on modified files
- push hook: Desktop check and 3,783 Desktop tests passed
- GitHub CI green except Desktop E2E Relay still running at last status
snapshot

---------

Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Signed-off-by: kenny lopez <klopez4212@gmail.com>
Co-authored-by: Joah Gerstenberg <joah@squareup.com>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Co-authored-by: kenny lopez <klopez4212@gmail.com>
2026-07-30 07:04:49 -07:00
7fb008f934 fix(desktop): explain open agent access (#2561)
## Why

Hack-day feedback exposed a dangerous mismatch between the UI and the
underlying access model. The `Anyone` respond-to mode appeared as a
neutral dropdown choice, while a Buzz agent may act with the files,
accounts, and tools available on the machine where it runs.

People reasonably read this as sharing a bot in a channel. The current
UI did not explain that it can also share the agent's available access.

## What

- Reframes `respond-to` as **agent access** in user-facing UI.
- Uses plain audience labels: **Only me**, **Anyone**, and **Selected
people**.
- Warns for **both** sharing modes, not just `Anyone` — `Selected
people` also hands host access to someone other than the owner, so only
the audience phrase differs:
> Anyone can use this agent to access your computer, including files,
accounts, and connected tools.

> Selected people can use this agent to access your computer, including
files, accounts, and connected tools.
- Names the machine the agent actually runs on. A provider-backed
(remote) agent reads:
> Anyone can use this agent to access the server it runs on, including
any accounts and tools available there.

The remote wording deliberately omits the owner's files — those aren't
theirs to describe on a host they don't own.
- Places the warning below the selector for `Anyone`, but **after** the
people picker for `Selected people`, so it never sits between the user
and the selection they came to make.
- Removes Nostr, harness, pubkey, and `!shutdown` jargon from the
primary decision copy. Direct pubkey entry remains available as an
advanced path.
- Replaces the green open-access avatar dot with an amber warning marker
and accessible text. Selected access uses a separate blue status.
- Aligns the sidebar action and profile field with the same language.
- Records the shared-field disclosure contract in
`desktop/src/features/agents/AGENTS.md` so future surfaces do not
silently omit it.

## Design decisions

**Persistent inline warning, not a confirmation modal.** The setting
does not autosave; the consequence remains visible beside the selection
until the person chooses **Save access**. This gives the information
before commitment without adding a dismiss-and-confirm ritual that would
repeat in every create/edit surface.

**An unknown run location falls back to the local wording.** It does not
hedge with "computer or server". A remote host requires an installed
`buzz-backend-*` provider, and without one `WhereToRunSection` never
renders — so "server" would name a concept the owner has never been
shown. When it *is* remote, they picked that host from the selector
themselves. Surfaces never synthesize a run location they don't have.

**One resolution site, published through context.** `AgentDialog`
resolves the run location (`runLocationForBackend` from
`ManagedAgent.backend`, `runLocationForRunOn` from the create flow's
`WhereToRunDraft`) and publishes it via `AgentRunLocationContext`. It is
not threaded as a prop through `AgentDefinitionDialog` (1047 lines) or
`AgentInstanceEditDialog` (1228 lines) — neither uses the value, and
both are already over the file-size ceiling. Surfaces outside that tree
(`EditRespondToDialog`) pass the prop directly.

The copy follows the writing system's guidance for high-sensitivity
decisions: lead with the material consequence, use plain actor/action
language, keep helper text adjacent and persistent, and never rely on
color alone.

## Scope

Desktop only. The web and mobile clients do not currently expose this
setting. No protocol, gate, runtime, persistence, or backend behavior
changes.

This does not add team-scoped remote agents. It makes the current
local-or-remote access model honest while that product work remains
separate.

## Validation

- `pnpm exec biome check` and `pnpm exec tsc --noEmit` — clean
- `lib/agentAccessWarning.test.mjs` (8/8) — every mode × run-location
copy variant, both resolvers, unknown-reads-as-local, blank `runOn` is
not a provider
- `ui/respondToFieldContract.test.mjs` (8/8) — plain labels, both
warning positions, source-order guard that the `allowlist` warning
follows the picker, helper-not-inline-copy guard
- `agent-access-warning.spec.ts` (3/3) — native local, provider-backed
remote (asserts the server sentence and *not* "your computer"),
persona-backed edit; includes a bounding-box check that the `Selected
people` warning renders below the picker

---------

Signed-off-by: David Hamilton <daveh@squareup.com>
Signed-off-by: Clay Delk <clay.delk@gmail.com>
Co-authored-by: Clay Delk <clay.delk@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 13:32:40 +00:00
thomaspblockandGitHub 3b8567a05d fix(desktop): remove Projects overview card fills (#3416)
## Summary

The Projects overview now lets the page surface flow through its metrics
and activity cards instead of stacking filled panels. Borders and hover
feedback remain, preserving grouping and interaction cues without the
heavy nested background.

### Related issue

None found.

### Testing

- `pnpm exec biome check
src/features/projects/ui/ProjectsOverviewPanel.tsx
src/features/projects/ui/ProjectsActivityFeed.tsx
tests/e2e/project-pr-review.spec.ts`
- `pnpm build:e2e`
- Focused Playwright smoke test: `project overview does not paint a
background behind its cards` (passed)
- Relevant desktop pre-push checks passed; the unrelated integration
gate was blocked by a stale local checksum for migration 25

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
2026-07-30 15:04:03 +02:00
788b3c002b fix(git): channel binding tooling + author remediation for unbound repos (#3626)
Closes #3527.

Repos announced via vanilla NIP-34 (kind:30617 without a `buzz-channel`
tag) 404 forever: the SEC-005 read gate requires a channel-membership
ACL, and nothing tells the author why or how to fix it. Per the ruling
in the originating thread, this ships **bind/rebind tooling plus a
narrow author-only remediation carve-out** — the shelved owner-circle
approach is intentionally absent.

## Relay
- **`api/git/binding.rs` (new):** shared tri-state binding resolver —
`Bound(uuid)` / `NotBound` / `Broken`. First-tag, fail-closed: a
malformed `buzz-channel` tag is `Broken`, never conflated with "no tag".
Both gates use it.
- **Read gate (`transport.rs`):** a **never-bound** repo read by **its
own announcement author** still returns 404 (status byte-identical to
the generic denial) but the body carries remediation: `run: buzz repos
bind --id <repo> --channel <channel-uuid> — …`. This leaks nothing — the
author announced the repo, and only the author can rebind (30617 is
keyed by `(author, d)`). `Broken` bindings stay generic-denial for
everyone, including the author (revocation shape).
Bound-to-nonexistent-channel stays generic (phase 1; ingest validation
is phase 2).
- **Push gate (`policy.rs`):** unbound denial now returns
`GIT_NO_CHANNEL_BINDING_BODY`. A deploy-skew test pins that the body
carries both the new token (`no_channel_binding`) and the legacy phrase
(`"no channel binding"`) so already-shipped desktops keep matching.
**(Review r1, blocker 2)** `Broken` no longer collapses into "unbound":
it denies 403 `invalid channel binding` for *everyone — including the
announcement owner —* **before** the owner short-circuit, matching the
read gate's fail-closed posture. The remediation token stays
NotBound-only.
- **`ingest.rs`:** side-effect failure `warn!` → `error!` — prod runs
`RUST_LOG=error`, so these failures were invisible during triage.

## Contract
- **`buzz-core/git_perms.rs`:** `GIT_NO_CHANNEL_BINDING_TOKEN` /
`GIT_NO_CHANNEL_BINDING_BODY` consts as the declared cross-component
contract; relay tests and desktop matcher both build on them.

## CLI
- **`buzz repos bind --id <repo> --channel <uuid>`** — rebinds an
existing announcement, preserving other tags.
- **(Review r1, blocker 1)** **`--channel` on `buzz repos create`** —
optional; injects exactly one shape-validated `buzz-channel` tag at
creation via a pure `build_create_announcement` builder, so the primary
create command stops producing repos the relay 404s. UUID
existence/membership stays the relay's authority at git-access time
(same TOCTOU posture as `repos bind`). Overlaps with #3594 (open, head
6bbe38459) — happy to reconcile whichever lands first; this branch also
carries the bind path and tag preservation.

## Desktop
- **Rust:** new `commands/project_git_merge_error.rs` (extracted from
`project_git_workflow.rs` to respect the 1000-line ratchet); maps the
token to a structured `no_channel_binding` error carrying the bind
command.
- **TS:** new `features/projects/lib/projectBranchErrors.ts` + tests —
dual matcher (new token AND legacy spaced phrase);
`ProjectBranchDialogs.tsx` uses it.

## Tests / verification (at head f914c7066, base 581baa625)
- Workspace `cargo test` green; `clippy -D warnings` clean; desktop Rust
1859 pass; TS 3780 pass; tsc/biome/file-size checks pass. Pre-push hooks
re-ran all suites at the pushed head.
- Postgres-gated `sec005_read_gate_tests`: all 6 pass, including
`read_gate_gives_author_of_unbound_repo_remediation_body` — asserts 404
status, `text/plain` content-type, and exact body bytes, distinguishing
remediation from generic denial (a blind `is_err()` can't).
- **New (review r1):** `buzz-cli` emitted-event tests —
`create_with_channel_emits_exactly_one_binding_tag`,
`create_without_channel_emits_no_binding_tag`,
`create_rejects_malformed_channel_uuid` (266/266 pass). Postgres-gated
`push_gate_denies_owner_through_broken_binding` — owner +
malformed-first/valid-second binding → 403 generic body without the
remediation token; never-bound control stays 200, pinning the denial to
`Broken` specifically.
- e2e git tests now bind announcements to a real channel via a
`create_test_channel` helper.

---------

Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
2026-07-29 20:07:55 -04:00
7012d86d52 feat: configure S3 URL addressing style (#3400)
## Summary

- add one strict `BUZZ_S3_ADDRESSING_STYLE=path|virtual` setting shared
by media and Git/CAS storage
- preserve path-style defaults for bundled Compose/Helm MinIO while
supporting Railway's virtual-hosted bucket contract
- fail startup on invalid or non-Unicode values before dependency
connection, and validate the Helm value with the same two choices
- document operator mappings and why endpoint and bucket remain separate
for routing and SigV4 signing

## Best-practice rationale

AWS documents both URL forms and favors virtual-hosted addressing for
S3, while compatibility endpoints such as the bundled MinIO deployment
can require path style. `rust-s3` defaults to virtual/subdomain
addressing and provides `with_path_style()` for the explicit
compatibility case.

Some providers buckets only support as virtual-hosted bucket styles.
This PR therefore uses one explicit, provider-neutral switch rather than
endpoint heuristics or fallback behavior, while retaining `path` as
Buzz's backward-compatible default.

Sources:
-
https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html
- https://docs.rs/rust-s3/0.37.0/s3/bucket/struct.Bucket.html
- https://docs.railway.com/storage-buckets#url-style
-
https://github.com/minio/minio/blob/master/docs/config/README.md#domain

## Validation

- `cargo fmt --all`
- `cargo check --workspace --all-targets`
- targeted `buzz-media` and `buzz-relay` parsing/client-construction
tests for defaults, strict errors, and both URL styles
- Helm unittest: 45/45 passed
- Compose config/render validation passed
- local MinIO path-mode relay startup passed the Git A3 conformance
probe and became ready
- unreachable object storage failed startup and readiness never opened
- push hooks completed the broader Rust and desktop suites successfully

---------

Signed-off-by: npub122y0pqkertljmedu303rl0aqrj3w8pvu43t6jxm6875lzg6f2pwqegc3xc <5288f082d91aff2de5bc8be23fbfa01ca2e3859cac57a91b7a3fa9f12349505c@buzz.block.builderlab.xyz>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
Co-authored-by: npub122y0pqkertljmedu303rl0aqrj3w8pvu43t6jxm6875lzg6f2pwqegc3xc <5288f082d91aff2de5bc8be23fbfa01ca2e3859cac57a91b7a3fa9f12349505c@buzz.block.builderlab.xyz>
2026-07-29 17:00:41 -07:00
ab55fee818 feat: add first-class OpenRouter provider support (#1975)
## Summary

First-class `Provider::OpenRouter` support joining the existing
anthropic/openai/databricks providers. Reuses the Chat Completions path
with targeted mutations for OpenRouter's routing contract.

**Core (`crates/buzz-agent`):**
- `Provider::OpenRouter` enum variant with `OPENROUTER_API_KEY`,
`BUZZ_AGENT_MODEL` → `OPENROUTER_MODEL` fallback, `OPENROUTER_BASE_URL`
env convention
- Body mutator: `reasoning: {effort}` when effort is configured, and
`max_completion_tokens` translated to OpenRouter's `max_tokens`
spelling; no `provider.require_parameters` filter (it routes only to
endpoints advertising every parameter in the body, which hard-404s a
valid model id); summaries get neither. `openai_body` is always called
with `effort=None` on the OpenRouter path — the `reasoning` object is
added by the mutator directly, so `reasoning_effort` is structurally
absent.
- Attribution headers: `HTTP-Referer: https://github.com/block/buzz`,
`X-OpenRouter-Title: Buzz`
- Error-inside-200 check in shared `parse_openai` (`finish_reason ==
"error"`)
- 401 auth handling: static API keys (`refresh_now` returns the same
token) fail terminal immediately with one wire request; PKCE/minting
sources get one retry with the fresh token.
- Status+`error_type` retry matrix (4-arm collapsed form): 429 (honor
`Retry-After`), 502 (retry), 503/`provider_overloaded` (honor
`Retry-After`), everything else including untyped 503 (bounded retries →
actionable routing message). 499 included matching shared `post()`
(#2175) for turn-timeout stall surfacing. Terminal failures wrapped in
`terminal_llm_error` for duration+attempt-count context.
- `anthropic/*` `cache_control` injection (model-gated, mixed-content
safe)
- Provider-agnostic `reasoning_details` opaque round-trip on
`HistoryItem::Assistant` for tool-call continuations — captured verbatim
in `parse_openai_with_reasoning_details`, replayed verbatim in
`openai_body`, byte-accounting charged. `provider_extra` passthrough
from `make_tool_call` composes independently.

**Desktop:**
- Readiness arms checking `OPENROUTER_API_KEY` + `OPENROUTER_MODEL`
- Model discovery via `{OPENROUTER_BASE_URL}/models` filtered on
`supported_parameters` contains `tools`
- Picker entry, credential config, effort table 3-file sync

**`desktop/src/features/agents/AGENTS.md`: no rules changed** — the
scoped rule requiring an explicit note is satisfied here.

Implements the gate-cleared plan from
`PLANS/OPENROUTER_PROVIDER_PLAN.md` (rev 3).

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-07-29 23:08:35 +00:00
WesandGitHub 3e48f1b236 chore(release): release Buzz Desktop version 0.5.2 (#3624)
## Buzz Desktop release v0.5.2

### Changes since v0.5.1:

- feat(cli): mirror Desktop mention delivery
([#3330](https://github.com/block/buzz/pull/3330))
([`7adc46268`](https://github.com/block/buzz/commit/7adc46268d5e93f0b1d4dc8e700af22815dcac1b))
- fix(desktop): deduplicate relay outage notification
([#3579](https://github.com/block/buzz/pull/3579))
([`66e705492`](https://github.com/block/buzz/commit/66e7054928cc29395f828467c3e8c81b7408dd29))
- fix(desktop): reconcile thread arrivals at bottom
([#3585](https://github.com/block/buzz/pull/3585))
([`b42a8d447`](https://github.com/block/buzz/commit/b42a8d447e3a2b85b2313dc4fdd123731fd8bba3))
- Improve emoji autocomplete matching
([#3571](https://github.com/block/buzz/pull/3571))
([`259de6afb`](https://github.com/block/buzz/commit/259de6afbe0cc0d106e57ebdb2323064990e4122))
- Fix shared agent avatar import profiles
([#3578](https://github.com/block/buzz/pull/3578))
([`324bd6b46`](https://github.com/block/buzz/commit/324bd6b464de5751e12abbd155376046ce3d2afc))
- Fix inline raster avatars in agent catalog
([#3581](https://github.com/block/buzz/pull/3581))
([`7e9b77f72`](https://github.com/block/buzz/commit/7e9b77f72d82e019a99f074f1c9829be30c57ae1))
- feat(agent): make Gemini and MLflow-route models usable through
databricks_v2 ([#3569](https://github.com/block/buzz/pull/3569))
([`4a1ebf25c`](https://github.com/block/buzz/commit/4a1ebf25c782fc6a68f0a69e6f866f793a259a1f))

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

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
2026-07-29 14:57:35 -07:00
Xule LinandGitHub 5aeed7c7a2 fix(desktop): discover bun-installed agent CLIs in ~/.bun/bin (#3343)
## Problem

`common_binary_paths()` probes mise shims, `~/.local/bin`, volta, asdf,
and (further down `resolve_command_uncached`) nvm's default bin dir —
but not bun's global bin directory, `~/.bun/bin`.

bun's installer appends its bin dir to `~/.zshrc` / `~/.bashrc`, which
are **interactive**-only. A login shell never sources them, so
`find_via_login_shell()` can't recover the path either. That's the same
failure mode already called out in this file for nvm:

```rust
// Check nvm's default Node.js bin directory — nvm initializes via
// ~/.zshrc (interactive) which is not loaded by a login shell, so
// `node`, `npm`, and npm-global shims installed there are otherwise
// invisible.
```

So for a GUI-launched desktop app, every rung of the resolution ladder
misses a bun-installed CLI:

1. workspace dev dirs — no
2. `command_looks_like_path` — no, presets use bare names
3. Buzz-managed npm/node dirs — no
4. current process PATH — launchd's minimal PATH on a Finder launch
5. `find_via_login_shell` — `.zshrc` not sourced
6. `common_binary_paths()` — **`~/.bun/bin` absent**
7. nvm default bin — no

This matters because bun is a common install route for the agent CLIs
Buzz targets. Kimi Code in particular ships as an npm package
(`@moonshot-ai/kimi-code`), so `bun add -g` puts it at `~/.bun/bin/kimi`
— exactly where discovery doesn't look.

## Reproduction

On macOS with `codex` and `kimi` installed via bun, launching Buzz from
Finder:

- Kimi Code shows **"CLI needed"**
- both CLIs run fine in an interactive terminal

Probing the way `find_via_login_shell` does, in a clean environment:

```console
$ env -i HOME=$HOME /bin/zsh -l -c 'command -v -- codex; command -v -- kimi'
(nothing)
```

Launching the app with the bun dir on PATH resolves both immediately:

```console
$ env PATH="$HOME/.bun/bin:$PATH" /Applications/Buzz.app/Contents/MacOS/buzz-desktop
```

## Change

One entry appended to the home-relative list in `common_binary_paths()`.
It goes **last** so it cannot shadow a directory that already resolves —
the change can only add resolutions, never alter existing ones.

## Testing

`cargo fmt --check` passes.

I was not able to run the full `just ci` gate locally: `ring 0.17.14`
fails to build in this environment against the macOS 26.2 SDK (`cc`
error compiling `p256-nistz.c`), which is unrelated to this change.
Relying on CI for the rest — the diff adds one `PathBuf` to an existing
`Vec<PathBuf>` and introduces no new API.

## Notes

- Related to #3084, which adds `~/.kimi-code/bin` for the same class of
GUI-launch discovery failure. That covers Kimi's standalone installer;
this covers the bun/npm-global install route. They're complementary —
I've left a note on that PR.
- Only `~/.bun/bin` is added. bun's global packages live under
`~/.bun/install/global/node_modules` but are symlinked into
`~/.bun/bin`, so the single directory is sufficient.
- Worth noting `~/.bun/bin` contains no `node`/`npm`/`npx`, so appending
it can't shadow a system Node toolchain.

Signed-off-by: Xule Lin <43122877+linxule@users.noreply.github.com>
2026-07-29 21:50:55 +00:00
7adc46268d feat(cli): mirror Desktop mention delivery (#3330)
🤖
## Summary

Agent-authored mentions currently depend on matching visible `@Name`
text to channel profiles. That makes notification delivery ambiguous
when names collide or profiles change, and it encourages an extra
post-send lookup just to confirm that the intended `p` tags were
emitted.

This change makes `buzz messages send` mirror Desktop's existing model:
the message keeps a readable name in its content while the recipient
pubkey is supplied separately.

```bash
buzz messages send \
  --channel <UUID> \
  --content '@Alice could you review this?' \
  --mention <alice-hex-or-npub>
```

`--mention` is repeatable. The CLI normalizes and deduplicates explicit
pubkeys, merges them with any names it can resolve from the channel, and
gives explicit identities priority under the existing 50-mention limit.

Before uploading attachments, signing, or publishing, the command checks
every resulting pubkey against the channel's current membership:

- Members are mentioned normally.
- Non-members stop the send and produce an actionable error.
- `--allow-non-member-mentions` deliberately sends notifying `p` tags
without adding anyone to the channel.

Sending a message never changes membership. On success,
`mention_pubkeys` is read from the exact signed event and returned with
the relay response, so callers can verify the emitted recipients without
another query.

Managed-agent guidance teaches this single-command mention flow. Desktop
mention behavior and the Nostr event schema are unchanged. Forum
guidance is intentionally handled separately in #3596.

### Related issue

None found. This replaces the earlier guidance-only approach in this PR
with the underlying CLI behavior it required.

### Testing

- `cargo test -p buzz-sdk`
- `cargo test -p buzz-cli`
- `cargo test -p buzz-acp`
- `cargo test --manifest-path desktop/src-tauri/Cargo.toml`

---------

Signed-off-by: npub1fdupjvyregj3z2tx7gx5x6py04zw89jm5usef9lyea4f3vcgh8qq9zgkdz <4b78193083ca25112966f20d4368247d44e3965ba7219497e4cf6a98b308b9c0@buzz.block.builderlab.xyz>
Signed-off-by: npub13n66s06epmqf2kc3v373ez8hj65cuzyvxzjf93vwpervxqn2u7jq2qd9je <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz>
Co-authored-by: npub1fdupjvyregj3z2tx7gx5x6py04zw89jm5usef9lyea4f3vcgh8qq9zgkdz <4b78193083ca25112966f20d4368247d44e3965ba7219497e4cf6a98b308b9c0@buzz.block.builderlab.xyz>
Co-authored-by: npub13n66s06epmqf2kc3v373ez8hj65cuzyvxzjf93vwpervxqn2u7jq2qd9je <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz>
2026-07-29 16:37:53 -04:00
66e7054928 fix(desktop): deduplicate relay outage notification (#3579)
🤖
## Summary
- Keep the relay reconnect notification dismissed across repeated
connection retries during one continuous outage.
- Re-arm the notification after recovery or relay lifecycle replacement,
including switches between communities that use the same relay URL.
- Preserve the existing dedicated path for authentication and other
application-level errors.

### How an outage is tracked
The AppShell-owned relay-card hook treats an outage as one contiguous
runtime episode rather than assigning it a persisted ID. A hook-local
`outageActiveRef` is armed by the first qualifying unreachable/degraded
observation. While it is armed, intermediate retry states (`connecting`,
`reconnecting`, `stalled`, and `disconnected`) belong to that same
episode, so retry churn cannot clear dismissal or emit another
notification.

The hook receives the same lifecycle identity used by community
initialization: community ID plus `reinitKey`. This distinguishes
multiple communities even when they share a relay URL, and it also
changes when the active community is explicitly reinitialized. The latch
and dismissal are reset when that identity changes or when the relay
singleton reports its authoritative `idle` teardown state. A successful
`connected` state also closes the episode and re-arms the next outage.

These boundaries deliberately bias toward re-notifying rather than
suppressing a later outage: recovery, community switch/reinit, or relay
teardown cannot leave the hook stuck believing an old outage is still
active. No outage state is persisted beyond the mounted hook lifecycle.

### Related issue
None found.

### Testing
- `pnpm --dir desktop typecheck`
- `pnpm --dir desktop test` — 3,769 passed
- `pnpm --dir desktop check`
- `pnpm --dir desktop build:e2e`
- `pnpm --dir desktop exec playwright test
tests/e2e/sidebar-relay-card.spec.ts --project=integration` — 11 passed

---------

Signed-off-by: Larry <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz>
Co-authored-by: npub13n66s06epmqf2kc3v373ez8hj65cuzyvxzjf93vwpervxqn2u7jq2qd9je <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz>
2026-07-29 15:48:06 -04:00
b42a8d447e fix(desktop): reconcile thread arrivals at bottom (#3585)
## Summary
- reconcile stale native-scroll anchors when a reply arrives at the
physical floor
- clear the thread new-message affordance instead of incrementing it
from stale cached state
- preserve the existing mid-history path and add direct lifecycle
regression coverage

## Why
PR #3411 fixed geometry-driven reconciliation, but the reply-arrival
branch still trusted a cached `message` anchor without checking the
rendered position. Native anchoring could return a short thread to the
floor without another scroll/resize callback, then the next reply
incremented the pill anyway.

## Verification
- Desktop checks passed
- Desktop typecheck passed
- focused lifecycle test passed (6/6)
- push hook full Desktop unit suite passed (3,770/3,770)
- `git diff --check` passed

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-29 12:46:47 -07:00
klopez4212andGitHub 259de6afbe Improve emoji autocomplete matching (#3571)
## Summary
- Show all colon emoji autocomplete matches
- Rank exact and prefix shortcodes before weaker matches
- Add a regression test and screenshot

## Validation
- `pnpm test`
- `pnpm build`
- `pnpm exec playwright test --project=smoke
tests/e2e/custom-emoji.spec.ts --grep "exact standard shortcode"`
- `just desktop-tauri-clippy`

Native Tauri tests were attempted but could not link because the local
disk filled during compilation.

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
2026-07-29 19:49:57 +01:00
324bd6b464 Fix shared agent avatar import profiles (#3578)
> Carl is updating this pull request on Wes's behalf.

## Summary

- upload an embedded raster avatar through the existing authenticated
media pipeline before minting or persisting an imported shared agent
- store and publish only the resulting hosted URL so agent kind:0
profiles remain within content limits

## Root cause

Snapshot import recovered raster avatar pixels as a large inline base64
data URL. That value was persisted and placed into the agent's kind:0
profile. The relay rejected the oversized profile, so other clients
could not resolve the imported agent's avatar.

## Scope

This is intentionally the forward fix only. It changes two Desktop files
and does **not** add migration or reconciliation behavior for previously
imported agents. Existing affected imports must be re-imported or fixed
manually.

## Validation

- successful pre-push Desktop suite: 1,863 passed, 14 ignored, 0 failed
- all pre-push Rust/Desktop gates green, including all-target clippy
- valid >256 KiB PNG import → production MIME detection/sanitization →
bounded signed kind:0 containing only the hosted URL
- upload failure, malformed data, and URL-only avatar cases covered
- independent fresh review by Princess Donut: clean, no blocking
findings

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-29 11:03:20 -07:00
7e9b77f72d Fix inline raster avatars in agent catalog (#3581)
## Summary

- render existing shared personas whose catalog avatar is a bounded
inline PNG, JPEG, GIF, or WebP data URL
- keep rejecting arbitrary, malformed, unsupported, and oversized
`data:` URLs
- preserve hosted relay URLs as the forward format; catalog browsing
remains read-only

## Root cause

Paul's live shared kind:30175 head contains a 144,878-character
`data:image/png;base64,...` avatar. The catalog projection accepted
HTTP(S) URLs and bounded percent-encoded SVG emoji avatars only, so it
projected Paul's avatar to `null` before `ProfileAvatar` rendered it.
The owner still saw the local persona avatar, producing the reported
owner/viewer mismatch.

This patch accepts only four raster MIME types with strict base64 shape
and a 256 KiB total URL cap at the existing catalog parsing boundary. It
repairs already-signed heads such as Paul without viewer-side uploads or
publication side effects.

Hosted media remains the canonical forward path. #3578 uploads inline
raster avatars during snapshot import, preventing the known source from
creating future inline persona/profile values; existing signed catalog
heads still need this compatibility path until their owners republish.

## Agent instruction finding

The catalog publishes `AgentDefinition.system_prompt` verbatim as the
user-authored **Agent instruction**, as documented by the sharing UI and
NIP-AP. No Buzz base/core/runtime prompt is concatenated in the publish,
catalog, or import path. This PR therefore does not remove authored
instructions and accidentally strip copied agents of their behavior.

## Validation

- targeted `personaCatalogRelay.test.mjs`: 24 passed
- Desktop typecheck: passed
- pre-push Desktop frontend suite: 3,771 passed
- pre-push Desktop checks: passed
- `git diff --check`: passed
- independent review: no blockers, 9.4/10

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-29 17:36:12 +00:00
WesandGitHub a13085e9ac chore(release): release Buzz Desktop version 0.5.1 (#3566)
## Buzz Desktop release v0.5.1

### Changes since v0.5.0:

- perf(desktop): move observer-feed archive and decrypt commands off
main thread ([#3415](https://github.com/block/buzz/pull/3415))
([`294c8c821`](https://github.com/block/buzz/commit/294c8c821de51442a8c384c0bdb66b1a10224ca0))
- fix(desktop): preserve shared agent fidelity
([#3553](https://github.com/block/buzz/pull/3553))
([`f7a3988ba`](https://github.com/block/buzz/commit/f7a3988ba13b590d9a55a7e8413fc3fb5ffbef18))
- feat(agent): route Claude/GPT model families to their native gateway
wire ([#3538](https://github.com/block/buzz/pull/3538))
([`6438dedf8`](https://github.com/block/buzz/commit/6438dedf83a9dbe1853e484326911bf6c7f1618c))
- Refine community invite limits
([#3529](https://github.com/block/buzz/pull/3529))
([`24d90d128`](https://github.com/block/buzz/commit/24d90d1280a9325c6cbcf8eea30ac54db5afd2cb))
- feat(agent): fix Anthropic prompt caching with Databricks (+ MCP
proxy/TLS passthrough)
([#3463](https://github.com/block/buzz/pull/3463))
([`c405ad1d4`](https://github.com/block/buzz/commit/c405ad1d4b1da061c11b3d26761252d41dcc62d3))
- feat: add explicit entry for claude-opus-5 in model config
([#2831](https://github.com/block/buzz/pull/2831))
([`90e058ebf`](https://github.com/block/buzz/commit/90e058ebf68137e048a409aec6616519379ff726))
- fix(desktop): clear stale thread new-message pill
([#3411](https://github.com/block/buzz/pull/3411))
([`55a3ed7b9`](https://github.com/block/buzz/commit/55a3ed7b9217cee5b23e0a5441947dc929b2a38c))
- fix(ci): ratchet file sizes against the base tree
([#3352](https://github.com/block/buzz/pull/3352))
([`9227bdf58`](https://github.com/block/buzz/commit/9227bdf58ad6664ae3c1078888f2181ec19c4da4))
- feat(desktop): apply WebKit rendering workarounds at startup on Linux
([#3271](https://github.com/block/buzz/pull/3271))
([`3ece4461d`](https://github.com/block/buzz/commit/3ece4461df8a7b9663a8e68327483b8377d4086d))
- fix(desktop): stabilize flaky DM expansion E2E ordering assertions
([#2004](https://github.com/block/buzz/pull/2004))
([`913d564ce`](https://github.com/block/buzz/commit/913d564ce0f35924291bf3eeab6508517a6d8d1f))
- fix(desktop): paint community rail full height
([#3382](https://github.com/block/buzz/pull/3382))
([`1d3b810ad`](https://github.com/block/buzz/commit/1d3b810ad70d6325718ed91e723f32c4a376d5e1))
- feat(desktop): add custom harness inline from agent dialogs
([#3252](https://github.com/block/buzz/pull/3252))
([`b0503d80c`](https://github.com/block/buzz/commit/b0503d80c298b1ece3b0a43b41d316829a3379e7))
- feat(desktop): refine agent catalog sharing
([#2439](https://github.com/block/buzz/pull/2439))
([`a35771fc4`](https://github.com/block/buzz/commit/a35771fc441cdc3c6f517f419037206783b502d2))
- fix(desktop): keep drafts out of the Inbox All view
([#3217](https://github.com/block/buzz/pull/3217))
([`3afa129ee`](https://github.com/block/buzz/commit/3afa129ee785cc74d921d0ba969254a8255c4cc0))
- fix(desktop): restore the inbox icon in the sidebar
([#3341](https://github.com/block/buzz/pull/3341))
([`00ede2e7a`](https://github.com/block/buzz/commit/00ede2e7aa7eb95571b7db3ebbd163adbf6cf74e))
- fix(desktop): gate codex-acp on a minimum supported version
([#3254](https://github.com/block/buzz/pull/3254))
([`4e3998f36`](https://github.com/block/buzz/commit/4e3998f36e36d68b9a93dcbd85f0864450bb8f5f))
- feat(cli): add users set-status command for NIP-38 profile status
([#3253](https://github.com/block/buzz/pull/3253))
([`60158fce3`](https://github.com/block/buzz/commit/60158fce3e670f11bb35d42627857ccaea50ff06))
- fix(composer): scope multiline block formatting
([#3246](https://github.com/block/buzz/pull/3246))
([`5457c947a`](https://github.com/block/buzz/commit/5457c947a74f5ba4b979f9c6411aa7626a858387))

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

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
2026-07-29 15:44:30 +00:00
Will PflegerandGitHub 294c8c821d perf(desktop): move observer-feed archive and decrypt commands off main thread (#3415)
Opening the agent observer feed could beachball the app. In Tauri 2, a
sync (`pub fn`) command body runs on the **main thread** — only `async
fn` commands run on the runtime pool. Five commands on the observer-feed
open path were sync, so panel open ran SQLite I/O and secp256k1 work on
the macOS main thread:

| Command | Main-thread work |
|---|---|
| `decrypt_observer_event` | Schnorr ID + signature verify, then NIP-44
decrypt — once per frame |
| `read_archived_observer_events_for_channel` | Opens the archive DB,
runs the channel-index JOIN, returns up to 200 raw JSON blobs per page |
| `read_unindexed_observer_rows` | Opens the DB, returns **all**
not-yet-indexed kind-24200 rows in one shot |
| `index_observer_channel_id` | Opens the DB, loops N upserts |
| `delete_save_subscription` | Opens the DB, one delete |

Eager hydration loads up to 10 pages × 200 frames on panel open, so
that's up to 10 main-thread DB reads plus up to 2,000 sequential
verify+decrypt calls before any scrolling. The one-shot backfill makes
it worse on the first open after history accumulates: one read of every
unindexed row, a decrypt per row, then a batch upsert — all on the main
thread, and all proportional to archive size.

The four archive commands now route their DB work through the existing
`run_archive_db_task` helper (`spawn_blocking` + `open_db`), matching
`list_save_subscriptions`, `read_archived_events`, and `archive_events`
directly around them. `decrypt_observer_event` becomes `async fn` +
`tauri::async_runtime::spawn_blocking`, with `state.signing_keys()`
extracted before the spawn since `State` is not `Send` — the same
pattern `sign_event` uses from #1222.

No frontend changes: `invoke` is already promise-based, so the TS
wrappers in `tauriArchive.ts` and `tauriObserver.ts` are unchanged.

This removes the freeze, not the work. Eager hydration still takes the
same wall time — the feed shows a loading state instead of blocking the
UI. Batching the per-frame decrypt IPC (2,000 round-trips into one
command) would cut the latency itself; that's deliberately out of scope
here.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-29 11:17:35 -04:00
f7a3988ba1 fix(desktop): preserve shared agent fidelity (#3553)
## Summary

Fixes two distinct fidelity failures in direct agent sharing:

- The sender now puts the same effective avatar shown on the agent card
into People-share and file-export snapshot PNGs, including
profile/kind:0 fallback avatars.
- The importer now persists the visible PNG body as the portable avatar
instead of ignoring it in favor of sender-local manifest references.
- Export materializes inherited runtime, provider, and model identifiers
verbatim, while preserving explicit definition values. It does not
translate or substitute configuration for a different recipient setup.
- Sharing waits for a profile-only fallback avatar query, preventing an
early-click race.

The PNG import path keeps the existing safety invariant: decode is
capped at 2048×2048 / 32 MiB and re-encoded avatars above the 2 MiB
inline limit fall back to the manifest reference. The exact transparent
1×1 no-avatar placeholder is ignored.

The original Tyler↔Wes screenshot demonstrates both stages: Wren's
attachment had an avatar that disappeared after **Add agent**
(receiver/import failure), while Pinky's attachment was already blank
(sender/projection failure).

### Related issue

N/A — reported and traced in the linked Buzz conversation.

### Testing

- `cargo test --manifest-path desktop/src-tauri/Cargo.toml
commands::personas::snapshot` — 57 passed
- `pnpm exec tsc --noEmit`
- Biome check on changed frontend/E2E files
- Pre-push hooks:
  - desktop check
  - desktop tests
  - desktop Tauri tests — 1853 passed, 14 ignored
  - file-size ratchet

The People-share E2E regression asserts that a profile-only avatar
reaches `avatarPngDataUrl` in the real encode command payload.

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-29 08:13:54 -07:00
klopez4212andGitHub 24d90d1280 Refine community invite limits (#3529)
## Summary

- Simplify the community invite dialog around link sharing.
- Add matching expiry and use-limit dropdowns, with sensible preset use
caps.
- Cover the default unlimited and selected-limit invite payloads.

## Validation

- `pnpm -C desktop run build:e2e`
- `pnpm -C desktop exec playwright test
tests/e2e/invite-link-copy.spec.ts
tests/e2e/invites-settings-screenshots.spec.ts --project=smoke`

Signed-off-by: kenny lopez <klopez4212@gmail.com>
2026-07-29 15:42:17 +01:00
90e058ebf6 feat: add explicit entry for claude-opus-5 in model config (#2831)
Fixes #2787

- Added `claude-opus-5` to `config.rs` model classification and adaptive
effort helpers.
- Updated fixture test configurations to cover `claude-opus-5`.
- Verified with `cargo test` and JS unit tests.

Signed-off-by: Apurva Shaw <apurvashaw@Apurvas-MacBook-Air.local>
Co-authored-by: Apurva Shaw <apurvashaw@Apurvas-MacBook-Air.local>
2026-07-28 22:56:20 +00:00
WesandGitHub 55a3ed7b92 fix(desktop): clear stale thread new-message pill (#3411)
## Summary
- reconcile anchored-scroll state when passive layout changes put a
thread at its physical floor
- route thread composer-padding growth and shrink through the same
hook-owned settlement path
- preserve pinned thread targets while clearing stale new-message state

## Root cause
Thread bottom state was updated primarily by native `scroll` events.
Deferred replies, viewport changes, and composer-overlay padding can
finish changing geometry after the user's last scroll—or after the
initial open pin—without another scroll event. The thread could visibly
reach the floor while `isAtBottom` and `newMessageCount` remained stale,
leaving the “N new messages” pill visible.

## Verification
- `pnpm check`
- `pnpm typecheck`
- `pnpm test` — 3,768 passed
- push hook: branch-skew, Desktop check, and Desktop full unit suite
passed

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
2026-07-28 15:35:10 -07:00
9227bdf58a fix(ci): ratchet file sizes against the base tree (#3352)
## Summary

- replace the whole-tree file-size gate with a stateless differential
ratchet
- allow inherited files over 1,000 lines to hold or shrink, but never
grow
- delete the 44-entry numeric override ledger and run the same policy
across Desktop, Web, and Mobile CI
- fail closed when the local base cannot be resolved and cover policy,
Git status parsing, and base resolution in unit tests

This removes the shared mutable policy state that caused unrelated PRs
to fail after neighboring merges. It does **not** by itself prevent two
stale green PRs from becoming invalid when combined; that requires merge
queue or up-to-date branch enforcement.

### Related issue

None found. This follows the design discussion in the linked Buzz
channel.

### Testing

- `node --test scripts/check-file-sizes-core.test.mjs` (6/6)
- Desktop, Web, and Mobile ratchet entrypoints
- `just desktop-check`
- `just web-check`
- Mobile analysis
- `git diff --check`

The repository pre-push suite also exposed an unrelated existing Mobile
widget failure in `ChannelDetailPage keeps follow mode off while a tall
newest message stays visible`; it reproduces in isolation and this
branch does not touch Mobile widget behavior.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-28 22:17:36 +00:00
Will PflegerandGitHub 3ece4461df feat(desktop): apply WebKit rendering workarounds at startup on Linux (#3271)
On some Linux GPU/driver/compositor combinations, WebKitGTK's dmabuf
renderer aborts the web process during startup, so Buzz comes up with no
window at all and the user has no way to fix it. Setting
`WEBKIT_DISABLE_DMABUF_RENDERER=1` avoids the abort by falling back to
the shared-memory buffer path.

WebKit reads each of its rendering variables exactly once per process,
so the choice has to be made before anything initializes — there is no
runtime toggle and no second chance later in the same process. This
decides up front from two cheap preflight signals rather than reacting
to a crash:

- **NVIDIA GPU** — any DRM device under `/sys/class/drm` reporting PCI
vendor `0x10de`, the driver family behind most upstream reports.
- **AppImage** — the `APPIMAGE` environment variable. linuxdeploy's
AppRun hook pins `GDK_BACKEND=x11`, and the dmabuf renderer buys nothing
on that XWayland path.

Either signal disables the dmabuf renderer. Neither signal leaves the
environment untouched.

## Escape hatches

`--safe-rendering` forces the safest configuration for one launch —
`WEBKIT_DISABLE_DMABUF_RENDERER` plus `WEBKIT_DISABLE_COMPOSITING_MODE`
— for a machine neither signal recognises.

Any user assignment of a variable this module may set stands the
heuristic down **wholesale**. Presence is the test, not truthiness, so
`VAR=0` and `VAR=` both count: a user asking for the dmabuf renderer
*on* gets it, even on a machine the heuristic would have opted out.
`--safe-rendering` against such an assignment is refused with a
diagnostic naming both the assignment and the key to unset, and exits
non-zero — the flag and the environment are two incompatible answers to
one question, and neither is guessed.

## Placement

`webkit_rendering::apply()` runs at the top of `fn main()`, before
`buzz_lib::run()`. That is the only point where the process is still
single threaded with no GTK object alive, which is what makes
`std::env::set_var` sound; the module doc and the call site both say so.
The whole module is `#[cfg(target_os = "linux")]` — macOS and Windows
compile none of it.

The decision is a pure function of argv, an injected environment lookup,
and an injected DRM root, so all of it is unit-testable without mutating
the process environment.

Closes #2338. Upstream:
[tauri#9394](https://github.com/tauri-apps/tauri/issues/9394). Same
approach and same variable as
[clash-verge-rev](https://github.com/clash-verge-rev/clash-verge-rev/blob/main/src-tauri/src/utils/linux/workarounds.rs)
`workarounds.rs` and
[screenpipe](https://github.com/screenpipe/screenpipe/blob/main/apps/screenpipe-app-tauri/src-tauri/src/linux_webkit_env.rs)
`linux_webkit_env.rs`.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-28 17:20:29 -04:00
913d564ce0 fix(desktop): stabilize flaky DM expansion E2E ordering assertions (#2004)
## Summary

Fixes 4 flaky DM expansion E2E tests in Desktop Smoke shard 1 that were
failing non-deterministically on CI (also reproducing on `main` at run
`29526844596`).

**Failing tests:**
- `channels.spec.ts:652` — creates the DM before preparing a persona
mention
- `channels.spec.ts:760` — routes an agent mention from an existing DM
to the expanded conversation
- `channels.spec.ts:815` — routes a relay-agent mention from an existing
DM to the expanded conversation
- `channels.spec.ts:940` — drops an expanded DM after the first message
fails

## Root Cause

Race condition: under fast CI execution, mock command completions
(create_managed_agent, open_dm) can resolve in non-deterministic order,
causing assertions to observe stale or mid-transition state.

## Fix

- **:652** — Move the `new-message-recipient-popover` hidden assertion
after `chat-title` settles (both names present), so it runs
post-transition rather than mid-transition.
- **:760, :940** — Add `createManagedAgentDelayMs: 100` to ensure
persona provisioning doesn't collapse into the same tick as the
expanded-DM open/start sequence.
- **:815** — Add `openDmDelayMs: 100` so the two open_dm calls resolve
in deterministic order.

## Validation

All 4 tests pass with `--repeat-each=3` (12/12 green) locally. Biome
lint clean.

## Scope

Test-only change: 12 insertions, 1 deletion in
`desktop/tests/e2e/channels.spec.ts`.

---

Investigated by Ferret, reviewed by Grumplestiltzkin.

Signed-off-by: Cameron Hotchkies <chotchkies@block.xyz>
Co-authored-by: Goose <opensource@block.xyz>
2026-07-28 14:17:17 -07:00
1d3b810ad7 fix(desktop): paint community rail full height (#3382)
## Summary

- paint the community rail across the full app height instead of
exposing the parent background through external margins
- preserve the existing community-button alignment and balanced
horizontal gutters by moving vertical spacing inside the rail
- update the rail geometry coverage to require full-height paint
ownership

## Root cause

PR #2972 aligned the rail box with the inset content by adding top and
bottom margins to the `bg-sidebar` element. Margins are outside the
painted box, so flat light and dark themes exposed a differently colored
app background above and below the rail.

## Validation

- pre-push `desktop-check`
- pre-push desktop unit suite: 3,751 passed
- `git diff --check`

Local Playwright/E2E was not run; CI owns the full browser matrix.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-28 20:17:01 +00:00
Will PflegerandGitHub b0503d80c2 feat(desktop): add custom harness inline from agent dialogs (#3252)
Registering a custom ACP harness works today, but only from Settings →
Agents. Anyone whose first touchpoint is "New agent" has no way to
discover the custom path — the dropdown just lists the baked-in presets
plus whatever was registered earlier. This adds an inline "Add custom
harness…" entry to the harness dropdown in all three agent surfaces:
create, edit-definition (`AgentDefinitionDialog`), and instance edit
(`AgentInstanceEditDialog`).

The entry is a sentinel option (`ADD_CUSTOM_HARNESS_VALUE`, NUL-prefixed
so it can never collide with a real harness id — backend ids match
`[a-z0-9_][a-z0-9_-]*`), mirroring the `CUSTOM_ENTRY_ID` trick already
used in `HarnessCatalogDialog`. Picking it never writes into form state;
it opens `AddCustomHarnessDialog`, a thin modal wrapper hosting the
existing `CustomHarnessForm` in `chromeless` mode. `CustomHarnessForm`'s
`onSaved` now carries the saved `definition.id` (the form may rewrite
it); the two existing call sites ignore the argument, so their behavior
is unchanged.

Selection after save is deferred rather than immediate.
`usePendingHarnessSelection` holds the saved id until the runtime
catalog actually publishes it via discovery, then selects it exactly
once — so the dialog never selects an id it cannot render, and
back-to-back registrations resolve correctly. The wait is scoped to the
owning dialog's `open` state: both host dialogs stay mounted when
closed, so an unpublished id is dropped on close rather than selecting
into reset form state when discovery later catches up. Selection is
routed through each dialog's normal dropdown change handler, so
provider/model reset (and command pinning in the instance dialog) behave
identically to a hand-picked harness. Dismissing the modal leaves the
previous selection untouched. `AgentInstanceEditDialog`'s existing
"Custom command" option is a different feature (ad-hoc command override
vs. a registered reusable harness) and is untouched.

Coverage is 16 unit tests in `addCustomHarness.test.mjs` (real React
mount, following the existing `.test.mjs` pattern) plus 4 Playwright
specs in `inline-custom-harness.spec.ts` covering all three surfaces
end-to-end. Both suites were mutation-verified: treating the sentinel as
a real selection, selecting before the catalog publishes, never clearing
the pending id, ignoring the dialog's open state, and reversing
latest-save-wins each turn the unit tests red; reverting the two dialog
diffs turns all four e2e specs red. The `check-file-sizes.mjs` overrides
for the two dialogs are ratcheted to their exact new counts (1048 and
1229) — verified tight in both directions, N passes and N−1 fails, so no
headroom is introduced.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-28 16:01:41 -04:00
a35771fc44 feat(desktop): refine agent catalog sharing (#2439)
## Summary

- add custom-agent catalog sharing and hide built-ins from discovery
- let owners publish later catalog updates from Share or while saving
edits — the save always persists locally, and the publish reports
`published` or `queued` (flushed automatically once the relay is
reachable again)
- preserve agent type, model, and runtime across snapshot import/export
- simplify agent and team entry points and tighten catalog layout
- migrate the legacy global retention queue into the owner's active
scope so pending catalog publishes survive the upgrade
- keep the agent list and edits usable in recovery mode by degrading to
unshared projections when scope resolution or the retention DB fails
- track catalog provenance on copied personas, so adding an
already-added foreign agent resolves to the existing copy instead of
creating a duplicate
- scope inbound persona events to the community relay they arrived on
- page the catalog read past the relay's 1,000-row query clamp
- unify the share dialog's memory-level choice into a single "What's
included" selector that drives both DM-send and copy-link delivery (all
six combinations preserved), group the delivery rows above the option
rows, and label the catalog toggle "Not shared" / "Shared"
- keep emoji avatars on catalog entries — they persist as inline
percent-encoded SVG, which the catalog projection's http(s)-only URL
guard used to drop, so a shared agent showed initials instead of its
avatar
- drop the "Active in communities" card from Agents settings, superseded
by the per-channel runtime controls in the members sidebar

## Screenshots

### Agent actions

![Agent
actions](https://raw.githubusercontent.com/block/buzz/4643581cd0882d8b101b04e3d8be290ea7e39f08/pr-2439--01-agent-menu.png)

### Team avatar stack

![Team avatar
stack](https://raw.githubusercontent.com/block/buzz/4643581cd0882d8b101b04e3d8be290ea7e39f08/pr-2439--02-team-menu.png)

### Catalog sharing

![Catalog
sharing](https://raw.githubusercontent.com/block/buzz/648d6eaf6df7d4daa5b7375948ed221f723793d6/pr-2439--03-share-to-catalog.png)

### Publish while editing

![Publish while
editing](https://raw.githubusercontent.com/block/buzz/bd4eb473ff456f6e665173054dc5a0f764594d1e/pr-2439--01-edit-agent-publish-updates.png)

### Publish from Share

![Publish from
Share](https://raw.githubusercontent.com/block/buzz/648d6eaf6df7d4daa5b7375948ed221f723793d6/pr-2439--02-share-dialog-publish-updates.png)

### Catalog details

![Catalog
details](https://raw.githubusercontent.com/block/buzz/4643581cd0882d8b101b04e3d8be290ea7e39f08/pr-2439--04-agent-catalog.png)

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-28 15:01:03 -04:00
thomaspblockandGitHub 3afa129ee7 fix(desktop): keep drafts out of the Inbox All view (#3217)
## Summary

Drafts were showing up in the Home Inbox **All** view, mixed in with
messages and reminders (reported in `#buzz-bugs`). Drafts are private
composer state, not inbox activity — they now appear only under the
dedicated **Drafts** filter.

## Changes

- **`inboxListRows.ts`** — drop the `draft` row variant from
`buildInboxListRows`; the mixed view builds only `inbox` + `reminder`
rows.
- **`InboxListPane.tsx`** — remove the draft branch of the All-view
render path; `PersonalItemRow` now renders reminders only.
- **`useHomePersonalInbox.ts`** — stop enabling draft selection (and its
root-status relay probing) for the mixed view; draft selection is scoped
to the Drafts filter.
- Drafts filter behavior is unchanged: the filter badge count,
`DraftsPanel` list, and `DraftDetailPane` all still work.

## Testing

- `pnpm test` (desktop unit suite): 3697 passed, 0 failed.
- `pnpm exec biome check src/features/home tests`: clean.
- Updated `inboxListRows.test.mjs` for the two-variant row model.
- Updated the e2e test (`channels.spec.ts`) to assert All never lists
drafts and that the draft is still reachable under the Drafts filter.
- Added `drafts-all-fix-screenshots.spec.ts` capturing both states
(screenshots below).

### All view — draft is gone, messages/reminders unaffected


![01-all-view-no-drafts](https://raw.githubusercontent.com/block/buzz/12c97624832cef40df951c403982994fea58dd80/pr-3217--01-all-view-no-drafts.png)

### Drafts filter — the draft is still listed and editable


![02-drafts-filter-still-lists](https://raw.githubusercontent.com/block/buzz/12c97624832cef40df951c403982994fea58dd80/pr-3217--02-drafts-filter-still-lists.png)

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
2026-07-28 19:37:12 +01:00
00ede2e7aa fix(desktop): restore the inbox icon in the sidebar (#3341)
## Why

The Inbox surface was briefly renamed to **Activity** during #2045 and
picked up a bell icon to match. The name was reverted to **Inbox**
before merge, but the icon was not.

A bell says "notification tray." Inbox is a destination — a focused,
conversation-oriented place to catch up on work relevant to you,
including drafts and reminders that have nothing to do with
notifications. The glyph should say that.

## What changed

- Swap the sidebar entry from Lucide `Bell` to Lucide `Inbox`.
- Assert the icon in `inbox-refactor-screenshots.spec.ts`. Nothing
pinned it before, which is exactly how it drifted through a rename.

This also brings desktop back in line with mobile, which already uses
`LucideIcons.inbox300` / `inbox500` for the same destination.

## Deliberately unchanged

The bell on **reminder** rows in the list pane (`InboxListPane.tsx`,
reminders → bell, drafts → file) stays. A bell is the right glyph for a
reminder; that one was never about the surface's identity.

## Verification

- The new assertion is a real guard, not a no-op: with `Bell` restored
the test fails with `Expected: 1, Received: 0` on `svg.lucide-inbox`.
Confirmed before committing.
- `biome` and `tsc` clean.
- Playwright smoke: `inbox-refactor-screenshots` 4 passed; `smoke`,
`navigation`, `channels`, `sidebar-more-unread-overlap`,
`home-collapsed-top-chrome`, `workspace-rail` — 107 passed, 1 skipped.
- Screenshot below is the regenerated `02-current-controls` shot from
the spec.

Signed-off-by: Clay Delk <clay.delk@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 14:01:18 -04:00
Will PflegerandGitHub 4e3998f36e fix(desktop): gate codex-acp on a minimum supported version (#3254)
The codex adapter version gate accepted any `major >= 1`, so a 1.x
`codex-acp` older than the version that fixes outbound relay access for
`buzz` CLI subprocesses classified as `Available` and was never offered
a reinstall. Only the 0.16.x `@zed-industries/codex-acp` adapter — which
fails `--version` outright — was caught.

`probe_codex_acp_version` now returns the full `(major, minor, patch)`
triple and `codex_adapter_availability` compares it against a new
`MIN_CODEX_ACP_VERSION` floor of `1.1.7`, the current npm latest. An
adapter below the floor classifies as `AdapterOutdated`, which routes it
through the existing uninstall-then-install reinstall plan.

The parse requires exactly three numeric dot-separated components.
Partial versions (`1.2`) and prerelease tags (`1.2.0-rc1`) return `None`
and therefore classify as `AdapterOutdated` — a version Buzz cannot
compare against the floor fails closed, offering a reinstall rather than
running an adapter of unknown vintage. Both the floor's bump policy and
the strict-parse behavior are stated in doc comments rather than left
implicit.

Supersedes [#3097](https://github.com/block/buzz/pull/3097) by
@Bharathchinneni, whose semver floor and behavior tests this carries.
That PR could not land as written: the two
`probe_codex_acp_major_version` compatibility wrappers it kept had no
non-test callers, which is a hard `clippy -D warnings` failure. The
wrappers are deleted here and their call sites collapsed onto
`probe_codex_acp_version`.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-28 13:56:59 -04:00
5457c947a7 fix(composer): scope multiline block formatting (#3246)
**Category:** fix
**User Impact:** Composer block formatting now applies to the intended
line or selection without collapsing multiline content.

**Problem:** Block formatting from a Shift+Enter line could convert the
entire draft, selected visual lines could collapse into one list item,
and code conversion could lose line breaks. **Solution:** Scope caret
formatting to its hard-break-delimited line and normalize explicit
selections for the destination block type while preserving neighboring
content and visual line boundaries.

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

**desktop/src/features/messages/lib/selectionBlockFormatting.ts**
Scopes collapsed-caret block actions to the active visual line and
normalizes multiline selections for lists and code blocks.

**desktop/src/features/messages/lib/selectionBlockFormatting.test.mjs**
Adds unit coverage for caret-line isolation across line positions and
selection directions.

**desktop/src/features/messages/ui/FormattingToolbar.tsx**
Routes list, quote, and code-block actions through the selection-aware
formatting transaction.

**desktop/tests/e2e/composer-selection-formatting.spec.ts**
Covers caret-only formatting, multiline list conversion, list-to-code
conversion, preserved hard breaks, Markdown output, and backward
selections.

</details>

## Reproduction steps

1. In the desktop composer, enter several lines using Shift+Enter and
place the caret on one line.
2. Apply a bullet list, ordered list, quote, or code block; only the
caret line should change.
3. Select several Shift+Enter lines and apply a list; each visual line
should become its own item.
4. Select several list items and apply Code block; they should become
one multiline code block while unselected neighbors remain intact.
5. Select several Shift+Enter lines and apply Code block; each line
break should remain visible.

## Screenshots/Demos
<img width="508" height="222" alt="Screen Recording 2026-07-27 at 5 29
19 PM"
src="https://github.com/user-attachments/assets/35640dea-0cfb-44f1-9b0b-a993c69cb55f"
/>

Expected multiline code-block result:
https://buzz.block.builderlab.xyz/media/d2e2668093af3b67d896a32e9799daccd236da9fc9e24ec56ddb4ebf7d01dd96.png

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
2026-07-28 13:31:27 -04:00
WesandGitHub 4a977c588a chore(release): release Buzz Desktop version 0.5.0 (#3213)
## Buzz Desktop release v0.5.0

### Changes since v0.4.26:

- feat(invites): add use-limited invite links
([#3141](https://github.com/block/buzz/pull/3141))
([`d500c2d5c`](https://github.com/block/buzz/commit/d500c2d5cf5d9aabe0ca4ebebfcafdbe5f5b7fd3))
- fix(node): bump Buzz-supplied Node runtimes past OpenClaw's >=24.15.0
floor ([#3218](https://github.com/block/buzz/pull/3218))
([`98a7b1334`](https://github.com/block/buzz/commit/98a7b1334823ee0be3e3fa5cab7a2e349e438dab))
- fix(desktop): preserve thread anchor through layout reflow
([#3212](https://github.com/block/buzz/pull/3212))
([`9810d8545`](https://github.com/block/buzz/commit/9810d8545937329f229ff40d8a19edc9e3e325c1))
- feat(search): parse from:/in:/after:/before: and pass them in the
filter ([#2871](https://github.com/block/buzz/pull/2871))
([`cb2a265b5`](https://github.com/block/buzz/commit/cb2a265b5399426e808461c1a16713754c593258))
- fix(desktop): fetch join policies through native networking
([#2862](https://github.com/block/buzz/pull/2862))
([`0019f8076`](https://github.com/block/buzz/commit/0019f80765e96f056e81b57789b8b5fb80936f72))
- fix(desktop): republish agent identity records when a persona rename
propagates ([#2607](https://github.com/block/buzz/pull/2607))
([`7ca0bbd94`](https://github.com/block/buzz/commit/7ca0bbd946fd82a7008132f94d069a97bb53f94b))
- fix(desktop): keep project Inbox previews compact
([#3193](https://github.com/block/buzz/pull/3193))
([`de1396050`](https://github.com/block/buzz/commit/de13960505fd798070e177cb33b1663100ac06bb))
- Inbox refactor ([#2045](https://github.com/block/buzz/pull/2045))
([`2bd4c24b7`](https://github.com/block/buzz/commit/2bd4c24b71335e7ce272ec6de6491f7f37f4b20d))
- Fix composer selection formatting and drop overlay
([#3172](https://github.com/block/buzz/pull/3172))
([`99da5b7eb`](https://github.com/block/buzz/commit/99da5b7ebb19e26453e075bfb949672122b31be3))
- Refine pending message status
([#3153](https://github.com/block/buzz/pull/3153))
([`75588eaff`](https://github.com/block/buzz/commit/75588eaff2354d620e554c055b80ec83735ddb0a))
- fix(desktop): recover full local storage on startup
([#3182](https://github.com/block/buzz/pull/3182))
([`174c38e4b`](https://github.com/block/buzz/commit/174c38e4bd1ed8498641546bc4fcb6d5a4c9cede))
- fix(desktop): keep collapsed table separators out of spoilers
([#3169](https://github.com/block/buzz/pull/3169))
([`4d8b676bb`](https://github.com/block/buzz/commit/4d8b676bb283a1917cec5850c3b7327fe122b0c1))
- feat(desktop): redesign agent runtime settings
([#3093](https://github.com/block/buzz/pull/3093))
([`d98da7389`](https://github.com/block/buzz/commit/d98da7389e60cfbd79b219aa411449fe2e53a18a))
- fix(desktop): use forward slashes for git credential.helper on Windows
([#3023](https://github.com/block/buzz/pull/3023))
([`899531684`](https://github.com/block/buzz/commit/8995316844f7ad50552fbae67fbd35119262796f))
- chore(desktop): add AgentCreationPreview file-size override to unblock
main CI ([#3154](https://github.com/block/buzz/pull/3154))
([`b92a1f4bf`](https://github.com/block/buzz/commit/b92a1f4bf400e7da5ab7a010cdd81a69497d8191))
- fix(desktop): make the test loader work on Windows
([#2758](https://github.com/block/buzz/pull/2758))
([`8bb43d519`](https://github.com/block/buzz/commit/8bb43d51912894553f2670b2d285a96cf09cd472))
- fix(desktop): make lint and unit-test gates work on Windows
([#2943](https://github.com/block/buzz/pull/2943))
([`545bb46b8`](https://github.com/block/buzz/commit/545bb46b824a3fbf4401062f03b72531d832ebb9))
- feat(desktop): add search to agent emoji picker
([#2630](https://github.com/block/buzz/pull/2630))
([`313f793c8`](https://github.com/block/buzz/commit/313f793c8753d413c22ff8edfe420d5ee78708bc))
- fix(desktop): keep identity key help dialog readable in dark mode
([#2854](https://github.com/block/buzz/pull/2854))
([`be275cfc6`](https://github.com/block/buzz/commit/be275cfc6c7b80fe43e9d66c6d14b6d2bbe58a10))
- feat(acp): title agent sessions from the agent and channel name
([#3028](https://github.com/block/buzz/pull/3028))
([`f2fe3b63c`](https://github.com/block/buzz/commit/f2fe3b63c21be55907175715c076cd3a9195b74d))
- feat(git): use agent display name as git author name
([#3040](https://github.com/block/buzz/pull/3040))
([`18eef633d`](https://github.com/block/buzz/commit/18eef633d88ac465c61d98f12655fbf51dc3ca44))
- fix(deps): bump nostr to 0.44.6 for RUSTSEC-2026-0216 (NIP-44 remote
DoS) ([#3135](https://github.com/block/buzz/pull/3135))
([`31e2de196`](https://github.com/block/buzz/commit/31e2de1966672e73e026af3c54f3a1a9a2f5e103))
- fix(desktop): read the newest pair-scoped harness log
([#3134](https://github.com/block/buzz/pull/3134))
([`654f38490`](https://github.com/block/buzz/commit/654f384906b5c720a60a199d85031a6f1cb6efc9))
- feat(desktop): handle project work from Inbox
([#3117](https://github.com/block/buzz/pull/3117))
([`c5c4f390b`](https://github.com/block/buzz/commit/c5c4f390b6713256e2efb8394c59823ebad73db6))
- fix(desktop): clarify identity key button when key exists
([#2357](https://github.com/block/buzz/pull/2357))
([`87b3fcd3c`](https://github.com/block/buzz/commit/87b3fcd3c0131683569dd4268b099d18b25dcd5e))
- Restore Goose and Buzz Agent to onboarding harness selection
([#2731](https://github.com/block/buzz/pull/2731))
([`7fc0cc82d`](https://github.com/block/buzz/commit/7fc0cc82db4d9dced9c258bbe8b530164a832a77))
- fix(desktop): render rich project work item content
([#3100](https://github.com/block/buzz/pull/3100))
([`afb272bb7`](https://github.com/block/buzz/commit/afb272bb7b8d7d45d7de676fa97dcd5a8eefacc7))
- feat(acp): bring your own harness (BYOH) — generic ACP runtime seam +
settings gallery ([#2773](https://github.com/block/buzz/pull/2773))
([`95fdf9788`](https://github.com/block/buzz/commit/95fdf978800982389b120c66ff5e766d785419c7))
- feat(desktop): use collective mesh routing for Auto
([#2825](https://github.com/block/buzz/pull/2825))
([`16d4ec335`](https://github.com/block/buzz/commit/16d4ec335e210295a9d9f77f36c1e85a18b6814a))
- fix(desktop): strip legacy baked team instructions from stored prompts
([#3035](https://github.com/block/buzz/pull/3035))
([`aee631448`](https://github.com/block/buzz/commit/aee63144843854ee32ed9d36a2e7511c82ddc6b0))
- feat(agents): lower default agent parallelism from 24 to 10
([#3038](https://github.com/block/buzz/pull/3038))
([`5d8ede446`](https://github.com/block/buzz/commit/5d8ede446f8fdc48146fe56d389cab6bf3500f92))
- Polish community rail and mobile pairing
([#2972](https://github.com/block/buzz/pull/2972))
([`e6c90bb7c`](https://github.com/block/buzz/commit/e6c90bb7c430d1b2af16508b634f9a5283b7fa3b))
- fix(desktop): remove bundled libsystemd from AppImage
([#2353](https://github.com/block/buzz/pull/2353))
([`a31fc4d2f`](https://github.com/block/buzz/commit/a31fc4d2f35d51cdf45ff8c61fc3a07f49c665e8))
- fix(desktop): make agent definition authoritative for
model/provider/prompt ([#1968](https://github.com/block/buzz/pull/1968))
([`8c0e8cb16`](https://github.com/block/buzz/commit/8c0e8cb1656b04ad269bce3c2deeda2a943ae78a))
- chore(desktop): delete dead persona catalog UI cluster
([#2886](https://github.com/block/buzz/pull/2886))
([`8e67cf399`](https://github.com/block/buzz/commit/8e67cf399d0291bcdbc69cd0402983ca030f05bb))
- fix(desktop): surface install failures hidden by curl-pipe exit codes
([#2892](https://github.com/block/buzz/pull/2892))
([`166c6655e`](https://github.com/block/buzz/commit/166c6655e8bca87d83ad60c087fb70a32a026baf))
- Refactor managed-agent runtime into cohesive modules
([#2974](https://github.com/block/buzz/pull/2974))
([`74b63e184`](https://github.com/block/buzz/commit/74b63e1846212af6e6751a62cfc631f74b1dfe07))
- fix(desktop): make Linux AppImage GStreamer work on non-Debian distros
([#2176](https://github.com/block/buzz/pull/2176))
([`cc6c4d347`](https://github.com/block/buzz/commit/cc6c4d3471629fad018bcf645f9471a01b9ffe2f))
- refactor(desktop): remove Agent directory section from Agents page
([#2290](https://github.com/block/buzz/pull/2290))
([`5d1233e84`](https://github.com/block/buzz/commit/5d1233e841b0efa91470bb45467b2c8e4284ebf6))
- fix(desktop): enable arboard Wayland backend so Linux copies reach the
Wayland clipboard ([#2904](https://github.com/block/buzz/pull/2904))
([`ab7aa8b12`](https://github.com/block/buzz/commit/ab7aa8b1200710dbc2d7a8661ed5aab95c4199c1))
- fix(desktop): supervise and re-arm relay-mesh runtime
([#2823](https://github.com/block/buzz/pull/2823))
([`aa51dab9d`](https://github.com/block/buzz/commit/aa51dab9da5fef7054d03cf1a1207986d0000684))
- fix(agents): run live Databricks discovery instead of the fallback
list ([#2890](https://github.com/block/buzz/pull/2890))
([`8eb6e3eb6`](https://github.com/block/buzz/commit/8eb6e3eb601174249642373a6a367262fa476753))
- fix(desktop): retire prepend mode on every reader wheel
([#2913](https://github.com/block/buzz/pull/2913))
([`07d0265cf`](https://github.com/block/buzz/commit/07d0265cfc212ef02e1c26153bf58ff46ce5ffe6))
- fix(desktop): consolidate prepend scroll correction
([#2855](https://github.com/block/buzz/pull/2855))
([`25e7864b3`](https://github.com/block/buzz/commit/25e7864b35f4dfd1c0ff31304a38555230a85f8d))
- fix(desktop): track concurrent agent turns up to the harness maximum
([#2882](https://github.com/block/buzz/pull/2882))
([`20bff5910`](https://github.com/block/buzz/commit/20bff591023daffc5ee1032cff02b54b75da3567))
- fix(relay): preserve reconnect backoff
([#2759](https://github.com/block/buzz/pull/2759))
([`499c5d349`](https://github.com/block/buzz/commit/499c5d349dab13bc906b1af5fe1fcb09ce2afa81))
- refactor(relay): expose reconnect timing policy
([#2310](https://github.com/block/buzz/pull/2310))
([`2f0041595`](https://github.com/block/buzz/commit/2f0041595d72529c06885680d2bd07ddb6a0beb4))
- fix(desktop): clear stale working badges on agent stop/restart
([#2803](https://github.com/block/buzz/pull/2803))
([`a64cc71f6`](https://github.com/block/buzz/commit/a64cc71f6c1605279b1a6fbd0fe904a2984cbdb0))
- fix(desktop): surface agent rename relay profile sync failure as a
warning toast ([#2279](https://github.com/block/buzz/pull/2279))
([`5e3d2e484`](https://github.com/block/buzz/commit/5e3d2e4849c0f2512330801d804fb96f4ab72d28))
- fix(discovery): inject PATH into Codex adapter planning
([#2767](https://github.com/block/buzz/pull/2767))
([`6ab3835f3`](https://github.com/block/buzz/commit/6ab3835f3fe89ee215819fe8d193463c0ae7472b))

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

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
2026-07-28 07:21:38 -07:00
Will PflegerandGitHub be13b4bb9c fix(desktop): probe legacy Goose install dir on Windows (#3248)
Goose's pre-[#2680](https://github.com/block/buzz/pull/2680) Windows
installer unpacked the CLI to `%USERPROFILE%\goose\goose.exe`. That
directory is on no standard `PATH`, and `common_binary_paths()` never
probed it, so users who installed Goose with the legacy installer stayed
permanently undiscovered — the residual half of #2239.

`resolve_command_uncached` finds binaries outside `PATH` only by
scanning `common_binary_paths()`, so adding the directory there is the
whole fix: Windows basename expansion already supplies
`goose.exe`/`.cmd`/`.bat`, and discovery, readiness probes, and spawn
all route through the same shared resolver. No Goose-specific resolution
path is introduced. The entry sits beside the existing Codex
`%LOCALAPPDATA%\Programs\OpenAI\Codex\bin` probe in the same
`#[cfg(windows)]` block.

The regression test is `#[cfg(windows)]` and is CI-reachable, not dead
code — the `desktop-build-windows` job runs `cargo test --manifest-path
desktop/src-tauri/Cargo.toml --target $env:TARGET` on `windows-latest`.
It asserts the probe list rather than planting a binary:
`common_binary_paths` is a process-lifetime `OnceLock`, so a test cannot
deterministically re-seed `USERPROFILE`, and planting an executable
under the real user profile is not an acceptable side effect. Verified
locally by widening the `cfg` to build on macOS — the test passes with
the probe and fails without it.

The `check-file-sizes.mjs` override for `managed_agents/discovery.rs`
moves 1835 → 1841, the exact post-`cargo fmt` gate count. Verified both
directions: 1841 passes, 1840 fails.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-27 21:30:21 -04:00
Will PflegerandGitHub 94675e0d25 refactor(desktop): extract install command execution into install_exec (#3251)
`commands/agent_discovery.rs` was pinned at its 2167-line file-size
ceiling with zero headroom, blocking the install-supervision and
install-log work queued behind it. Install command *execution* is a
clean seam and moves into `commands/agent_discovery/install_exec.rs`
together with its tests, matching the existing `managed_node.rs` /
`post_install_verification.rs` split under the same module.

Moved: `INSTALL_MAX_ATTEMPTS`, `run_install_command_with_retry`,
`run_install_with_retry`, `install_failure_is_retryable`,
`install_retry_backoff`, `annotate_retry_attempts`,
`run_install_command`, `truncate_output`, `floor_char_boundary`, and the
install-retry test block. Command *construction*
(`install_shell_command`, `install_powershell_command`,
`build_install_command`) stays in the parent — the new module owns only
what happens once a `Command` exists. Public surface is exactly one
`pub(super) fn run_install_command_with_retry`.

The extraction is behavior-preserving, verified by diffing the moved
text against the original line ranges: the parent is original-minus-cuts
plus the intended edits, and the moved code is byte-identical except for
the `pub(super)` marker, the `build_install_command` →
`prepare_install_command` call site, and the new function described
below. Two parent imports (`std::io::Read`, `InstallStepResult`) became
unused and were dropped.

### Install working directory (#2245)

Absorbed from #3090. A packaged desktop launch inherits `/` as its
working directory, so installers that write relative to the CWD fail on
a read-only root. The new `prepare_install_command` builds the command
and applies `default_agent_workdir()`, and it is the only builder
`run_install_command` calls — so no spawn path can bypass the workdir.

This differs from #3090 in the test: that version spawned `pwd` through
the real install shell and deleted
`test_install_shell_command_returns_ok_on_unix` to make room. Here the
prepared `Command` is asserted directly via `get_current_dir()` —
hermetic, no shell spawn — and the existing test is kept.

### Tests

Four new, on top of the moved retry block:

- `test_prepared_install_command_uses_default_workdir` — every install
child carries `default_agent_workdir()`.
- `test_truncate_output_leaves_short_output_untouched` — under the cap,
byte-for-byte passthrough.
- `test_truncate_output_keeps_head_and_tail_with_marker` — over the cap,
both ends survive and the marker names the omitted byte count.
- `test_truncate_output_does_not_split_multibyte_characters` — the
boundary floor prevents a mid-codepoint cut.

`truncate_output` had no coverage anywhere before this.

### File-size gate

`check-file-sizes.mjs` override for `agent_discovery.rs` moves 2167 →
1808, the exact post-`cargo fmt` gate count — verified both directions
(1808 passes, 1807 fails). `install_exec.rs` is 458 lines and needs no
override; the default 1000-line limit covers it.

Related: #3090, #2245

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-27 21:29:44 -04:00
morgmartandGitHub d8f9d87c17 Polish composer activity layout and transitions (#3151)
## Summary
- Replace the permanently reserved composer activity row with a
conditional, content-driven accessory for channel and thread composers.
- Keep the composer dock geometrically stable while activity appears, so
the composer’s bottom edge resizes smoothly without shifting the
conversation.
- Preserve translucent backdrop blur during the resize with a stable
dock-level blur layer, and keep thread overlay/focus-mode alignment
consistent.
- Keep bottom-pinned virtualized conversations fully visible as composer
content, zoom, or viewport height changes without repinning readers who
have scrolled into history.
- Refine the activity lockup with aligned avatars/text and a subtle,
reduced-motion-safe shimmer.
- Centralize the dock’s quiet inset, activity rail, released space, and
activity offset in one CSS-variable geometry contract.

This takes a different, systemic route from the spacing reduction
proposed in #2602 and supersedes that approach.

### Related issue
Related PR: #2602

### Testing
- `./scripts/check-branch-skew.sh`
- `just desktop-check`
- `just desktop-test` — 3,638 passing
- `useAnchoredScroll.test.mjs` — virtualized viewport resize follows the
explicit bottom state
- Focused desktop smoke E2E — 6 passing across stable dock geometry,
multiline growth, viewport resize, reduced motion, blur ownership, and
thread overlay alignment
- Pre-push hooks passed for organization safety, branch skew, desktop
checks/tests, Rust tests, workspace tests, and desktop Tauri tests
- Mobile pre-push is independently red on latest `main`; the exact
`activity_page_test.dart` compiler failure reproduces on untouched
`origin/main`
- Visually tested channel and thread composers across quiet/activity
states, multiline composer growth, and thread overlay/focus mode


https://github.com/user-attachments/assets/ed0b08d9-18e0-4061-b272-ab509dbcd8ee

---------

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
2026-07-27 15:47:04 -07:00
d500c2d5cf feat(invites): add use-limited invite links (#3141)
## Summary

- add database-backed v2 invite links with optional maximum-use limits
and atomic final-slot redemption
- preserve v1 invite compatibility while adding
exhausted/expired/invalid client handling across desktop, web, and
mobile
- emit structured claim-outcome logs with community, invite ID, outcome,
maximum uses, and post-claim count

## Verification

- `cargo fmt --all -- --check`
- `cargo test -p buzz-db` (85 passed, 134 Postgres-dependent ignored)
- `cargo clippy -p buzz-db --all-targets -- -D warnings`
- desktop `npm run typecheck`
- push hook: desktop checks/tests, desktop Tauri tests, Rust tests, and
branch-skew passed
- Postgres integration tests were previously reviewed green at the
pre-rebase tree; local rerun on this session was unavailable because
Postgres/Docker were not running
- mobile push-hook check could not start because Flutter is unavailable
locally

---------

Signed-off-by: Kalvin Chau <kalvin@block.xyz>
Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
Co-authored-by: npub1c4alndp82zyt9veaklm5d965quss79vlhk9awv7qu5erwhmf42qqlvc25c <c57bf9b4275088b2b33db7f746975407210f159fbd8bd733c0e532375f69aa80@buzz.block.builderlab.xyz>
Co-authored-by: npub122y0pqkertljmedu303rl0aqrj3w8pvu43t6jxm6875lzg6f2pwqegc3xc <5288f082d91aff2de5bc8be23fbfa01ca2e3859cac57a91b7a3fa9f12349505c@buzz.block.builderlab.xyz>
Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
2026-07-27 15:19:39 -07:00
98a7b13348 fix(node): bump Buzz-supplied Node runtimes past OpenClaw's >=24.15.0 floor (#3218)
## Problem

User report:

```
Buzz Node mismatch: Buzz supplies Node 24.14.0; OpenClaw requires >=24.15.0. All 10 ACP workers immediately crash.
```

Buzz supplies Node to agent processes from two places, and both were
below OpenClaw's floor:

| Supply path | Was | Now |
|---|---|---|
| hermit dev env (`bin/.node-*.pkg`) — the 24.14.0 in the report |
24.14.0 | **24.15.0** (newest hermit publishes; satisfies `>=24.15.0`) |
| Desktop managed runtime (`managed_node.rs` / `managed_node_paths.rs`)
| v24.11.0 | **v24.18.0** (current latest v24) |

The managed runtime sits **first** on the worker PATH
(`managed_agents/runtime/path.rs`), so a user-installed newer Node can't
mask a stale managed one — the pin itself has to move.

## Verification

- SHA-256 digests for all six platform artifacts taken from
`https://nodejs.org/dist/v24.18.0/SHASUMS256.txt`; darwin-arm64
independently re-verified by downloading the tarball (hash match),
extracting, and running `bin/node --version` → `v24.18.0`.
- All artifacts within `MANAGED_NODE_MAX_BYTES` (largest linux-x64 at 57
MB < 90 MB cap); tar.gz layout keeps the `node-vX-platform/bin/node`
shape `verify_node_tree` expects.
- `cargo test --lib` in `desktop/src-tauri`: **1801 passed, 0 failed**
at this commit; `cargo fmt --check` + `cargo clippy --lib -D warnings`
clean.
- Existing readiness check (`node --version == MANAGED_NODE_VERSION`)
makes upgrade automatic: installed v24.11.0 trees fail readiness and the
installer stages v24.18.0 atomically (rename with `.old` rollback —
existing logic, unchanged).

Note: CI `node-version: 24.14.1` pins in
`release.yml`/`windows-canary.yml` are build-env only (already `>=`
nothing OpenClaw touches) and left alone to keep this minimal.

Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
2026-07-27 22:18:02 +00:00
9810d85459 fix(desktop): preserve thread anchor through layout reflow (#3212)
## Summary
- keep a thread presentation-switch anchor pinned while focus/split
width reflow settles
- retire the temporary anchor only after resize correction and a
following paint confirm the row is visible
- preserve the existing external-target resolution behavior and viewport
E2E contract

## Root cause
The focus and split wrappers intentionally retain the same thread
surface, but switching wrappers also changes the message column width.
`useAnchoredScroll` centered the captured message once and immediately
cleared the one-shot layout target. A later text reflow could then move
that message outside the viewport with no remaining target to correct
it.

## Verification
- `pnpm check`
- `pnpm typecheck`
- `pnpm test` — 3,699 passed
- `pnpm build:e2e`
- `pnpm exec playwright test tests/e2e/thread-focus-mode.spec.ts
--project=smoke --repeat-each=10` — 20 passed
- push hook: branch-skew, Desktop check, and Desktop full unit suite
passed

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-07-27 14:54:52 -07:00
cb2a265b53 feat(search): parse from:/in:/after:/before: and pass them in the filter (#2871)
## Summary
- Adds a pure-TS operator parser (`from:` / `in:` / `after:` /
`before:`) with unit tests. Invalid date tokens stay in the FTS text.
- Extends `search_messages` so the desktop can send `authors` / `since`
/ `until` (and existing `#h`) on the filter the relay already
understands.
- Wires topbar search to strip operators from the prefix query, resolve
`in:` against local channels and `from:` against hex pubkeys / known
agents, then pass the structured fields through.

This is part 1 of #2853 (parser + command plumbing). Autocomplete chips
/ richer `from:@name` resolution can follow in a second PR.

## Test plan
- [x] `node --import ./test-loader.mjs --experimental-strip-types --test
src/features/search/lib/parseSearchOperators.test.mjs`
- [x] Added `search_messages_filter_emits_operator_fields` unit test
(full `buzz-desktop` crate build needs local sidecar binaries in this
environment)
- [ ] Manual: topbar `deploy from:<hex> after:2024-01-01` emits
authors/since on the bridge filter and returns narrowed hits

Made with [Cursor](https://cursor.com)

---------

Signed-off-by: Jatinder Mahajan <jatinder.mahajan@certifyos.com>
Co-authored-by: Jatinder Mahajan <jatinder.mahajan@certifyos.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-27 21:52:56 +00:00