Commit Graph

2646 Commits

Author SHA1 Message Date
John Tennant
cf4f091d2c Preserve supplied identity when sharing Buzz worktree identity
Signed-off-by: John Tennant <jtennant@squareup.com>
2026-09-09 12:30:53 -04:00
John Tennant
7f23539ed6 Document task assignee metadata
Signed-off-by: John Tennant <jtennant@squareup.com>
2026-09-09 11:08:00 -04:00
John Tennant
adbe110235 Merge remote-tracking branch 'origin/main' into jtennant/task-channel-experiment
* origin/main:
  fix(acp): pace targeted overflow recovery on consumer capacity (#7325)
  fix(link-preview): keep composer fetches user-paced (#7211)

Signed-off-by: John Tennant <jtennant@squareup.com>
2026-09-09 10:29:06 -04:00
John Tennant
e0ff4bfae9 Nest channel-backed tasks in sidebar
Signed-off-by: John Tennant <jtennant@squareup.com>
2026-09-09 10:22:49 -04:00
John Tennant
d2d58107df Clarify standalone task channel creation
Signed-off-by: John Tennant <jtennant@squareup.com>
2026-09-08 22:42:05 -04:00
John Tennant
6767cce8d2 Prototype channel-backed task views
Signed-off-by: John Tennant <jtennant@squareup.com>
2026-09-08 19:38:57 -04:00
Logan Johnson
c045321a7f fix(acp): pace targeted overflow recovery on consumer capacity (#7325)
🤖
## Summary

When a Buzz agent falls behind on incoming messages, its connection can
make the backlog worse while trying to recover. The connection buffers
messages from the relay server until the agent is ready to process them;
if that buffer overflows, recovery previously requested history for
**every subscribed channel** and paused socket reads while sending those
requests. That adds traffic to an already overloaded connection. This
change requests history only for affected subscriptions, once the code
consuming those messages has room, with at least five seconds between
attempts.

The recovery path now:

- Combines repeated losses into one pending recovery per affected
subscription, keeping the oldest dropped timestamp so replay starts
early enough.
- Waits until at least half the consumer queue is free and the relay's
existing rate-limit delay has expired. The queue wakes recovery when
space becomes available; recovery does not periodically sample capacity
or hold queue space away from live messages.
- Attempts one subscription at a time, choosing the least recently
attempted so a busy channel cannot crowd out other channels or
membership notifications. The five-second delay starts when an attempt
finishes, including a failed write; failed writes leave recovery
pending.

Recovery is paced by available capacity, not by how often messages are
lost. This is not a larger buffer or a cutoff that abandons recovery.
Subscription identifiers, message filters, replay timestamp overlap and
duplicate filtering are unchanged; no downstream agent changes are
required.

This targets a reproducible overload **amplifier**, not every cause of
overload or every catch-up limitation. The initial live overload's cause
has not been established. Recovery remains best effort: a successful
request write is not proof of delivery, and existing history/retention
limits, bounded duplicate tracking and replay limitations still apply.
There is no exactly-once or complete catch-up guarantee. A stalled write
can still pause socket reads for the existing ten-second timeout; the
pacing bound does not cover initial subscriptions, reconnects or other
retry paths.

### Related issue

Closest related: #5014 (channel re-subscription); also #6661 (membership
reconciliation) and #6090 (relay backpressure gap signaling). This
addresses local overflow recovery scheduling, not those separate
mechanisms.

### Testing

Recorded offline comparisons against the previous behavior, with the
final implementation at `8000636f3073167c5a5107bb179c7d91160f1729`:

| Same fixture: 18 subscriptions, three overload rounds | Before | After
|
| --- | --- | --- |
| Recovery history requests | 108 | 3 |
| Ping-response delay | About 4.6 seconds | Below the measurement's 1 ms
resolution |

A separate bounded-history fixture delivered all 320 events plus
subsequent live traffic in **both** versions. Regression coverage
exercises the real socket-handling task, including intermittent consumer
capacity, fairness, failed writes and cancellation of capacity waits
before live delivery. These are synthetic results, not production
throughput measurements or evidence of a deployed cure.

The full local `RUST_TEST_THREADS=4 just ci` run passed on September 4,
2026. Earlier unsuccessful local runs remain part of the validation
history. The [recorded validation evidence and separate desktop
follow-up](https://github.com/block/buzz/pull/7325#issuecomment-5540592398)
preserve the original desktop mock-history scroll failure, its passing
rerun and the remaining investigation. That desktop path does not run
the agent connection code; neither this repair nor the passing rerun
fixes the observed scroll problem.

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
2026-09-08 21:51:47 +00:00
John Tennant
bdd89522ca docs: capture unresolved branch binding flow
Signed-off-by: John Tennant <jtennant@squareup.com>
2026-09-08 16:55:14 -04:00
John Tennant
8a2a19d4cb docs: note automatic branch channel resolution
Signed-off-by: John Tennant <jtennant@squareup.com>
2026-09-08 16:53:54 -04:00
John Tennant
11e7e74ccb docs: clarify branch channel reuse
Signed-off-by: John Tennant <jtennant@squareup.com>
2026-09-08 16:53:29 -04:00
Taylor Ho
218633b8fd fix(link-preview): keep composer fetches user-paced (#7211)
**Category:** fix
**User Impact:** Link previews can keep loading while a message is being
composed, while sending still has a finite escape hatch and stalled
network transports cannot occupy preview slots forever.

**Problem:** Native metadata and image deadlines could collapse slow
previews into fallback cards while the user was still composing, and a
shared image-host cooldown made pasted batches fail inconsistently after
one rate limit. **Solution:** Keep preview resolution user-paced with no
aggregate request deadline, bound transport inactivity (15s DNS/connect,
30s idle read), serialize image requests by host, and allow at most one
server-directed cooldown wait of up to 30s across an image fetch and its
redirects. The existing bounded post-Send preparation and immediate Skip
paths remain unchanged.

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

**desktop/src-tauri/src/commands/link_preview.rs**
Removes aggregate native deadlines so composer metadata work can
complete at the user's pace, while retaining DNS/connect/idle-read
liveness bounds. Adds bounded host-paced image request coordination that
releases its gate during cooldown, waits inline at most once for at most
30 seconds, and cannot renew that wait through redirects or the outer
transient retry. Same-host image and favicon requests remain
deliberately serialized to align with host rate limits.

**desktop/src-tauri/src/commands/link_preview_rate_limit.rs**
Adds a fixed-size striped host gate so concurrent image requests are
serialized without retaining an unbounded attacker-controlled hostname
map.

**desktop/src-tauri/src/commands/link_preview_tests.rs**
Moves native link-preview tests into a dedicated module and covers the
user-paced metadata contract, bounded one-shot cooldown behavior, and
gate release while a rate-limited request sleeps—including a different
host sharing the same bounded gate stripe.

**desktop/src-tauri/src/commands/link_preview_youtube.rs**
Removes the thumbnail fetch deadline so YouTube previews follow the same
composer lifecycle contract while using the shared bounded transport.

**desktop/src/shared/lib/useResolvedLinkPreviews.ts**
Adds development-only metadata outcome diagnostics with elapsed time and
image/fallback state, without logging encoded image payloads.

</details>

### Reproduction steps

1. Open the desktop composer and paste several GitHub pull request links
whose OpenGraph images share a host.
2. Observe that image requests are paced by host instead of racing, and
slow-but-progressing preview work remains pending rather than
immediately becoming a completed favicon fallback.
3. Send while preview work is still pending and confirm **Preparing link
preview** remains bounded by the existing post-Send budget.
4. Use **Skip** during preparation and confirm the message proceeds
immediately.
5. In a development build, inspect the console for `[link-preview]
metadata fetch completed` diagnostics containing elapsed time and image
state without base64 payloads.

### Related issue

N/A — scoped from the linked Buzz implementation room.

### Testing

At current head `dfb394aafbee537e9ffb04ad3732d08f65f30b8e`:

- Production-bound paused-time metadata regression passed through
`fetch_link_preview_metadata`; restoring the former 10-second aggregate
wrapper makes it fail at the pending assertion.
- Native link-preview module: 19/19 passed.
- `cargo check --manifest-path desktop/src-tauri/Cargo.toml` passed.
- Rust formatting and `git diff --check` passed.
- Pre-push `push-head-scope`, org safety, differential file-size,
branch-skew, and `desktop-tauri-checks` hooks passed.

At prior head `59e2dcf167b15c7a3e637ad2608008b7f9cef5f3`:

- Full Tauri Rust suite: 3,056 passed, 19 ignored; integration crates 7
+ 3 passed.
- Focused native link-preview suite: 26/26 passed.
- The pasted multi-preview workflow was exercised in the desktop app and
confirmed improved before draft publication.

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@users.noreply.github.com>
2026-09-08 12:59:03 -07:00
John Tennant
ecee0b8ec7 docs: model branch-backed task relationships
Signed-off-by: John Tennant <jtennant@squareup.com>
2026-09-08 15:29:53 -04:00
John Tennant
60cf9bbe7a docs: require task experiment guidance
Signed-off-by: John Tennant <jtennant@squareup.com>
2026-09-08 14:43:53 -04:00
John Tennant
ebd6825db1 docs: show task experiment note
Signed-off-by: John Tennant <jtennant@squareup.com>
2026-09-08 14:16:30 -04:00
John Tennant
f90baaac09 docs: link task canvases to experiment
Signed-off-by: John Tennant <jtennant@squareup.com>
2026-09-08 14:10:46 -04:00
John Tennant
397f3dfc20 docs: clarify channel link placeholders
Signed-off-by: John Tennant <jtennant@squareup.com>
2026-09-08 14:07:30 -04:00
John Tennant
8cfcb9a14f docs: separate task origin from messages
Signed-off-by: John Tennant <jtennant@squareup.com>
2026-09-08 14:04:54 -04:00
John Tennant
d9560aca66 docs: link task creation conversations
Signed-off-by: John Tennant <jtennant@squareup.com>
2026-09-08 14:03:49 -04:00
John Tennant
67ac941a74 docs: keep canvas schema self-explanatory
Signed-off-by: John Tennant <jtennant@squareup.com>
2026-09-08 14:01:12 -04:00
John Tennant
d4d3411029 docs: trim canvas metadata guidance
Signed-off-by: John Tennant <jtennant@squareup.com>
2026-09-08 14:00:28 -04:00
John Tennant
3bb26c1dcf docs: define task canvas metadata
Signed-off-by: John Tennant <jtennant@squareup.com>
2026-09-08 13:59:46 -04:00
John Tennant
dba8cb4058 docs: describe channel-backed task experiment
Signed-off-by: John Tennant <jtennant@squareup.com>
2026-09-08 13:52:21 -04:00
Mic Neale
44316ff72f feat(mesh): upgrade to mesh-llm 0.76.0-rc8 and recommend Qwen3.8 27B (#6189)
Upgrades Buzz's mesh-llm dependencies through the released `v0.76.0-rc8`
tag (`2040765d`), including the Qwen3.8 curated recommendation and rc8's
scheduler/runtime improvements.

**Scope note:** the earlier open-relay/unenforced-admission mode has
been removed from this PR at Mic's direction — it is not a product mode
we want. Mesh admission remains roster/allowlist driven, exactly as on
`main`: on a relay with no NIP-43 membership snapshot the mesh runs
self-only. No NIP-11 mode probing, no mode-transition restarts. A future
perimeter/admission strategy for open relays will be designed
separately.

This PR also:
- seeds `BUZZ_AGENT_LLM_TIMEOUT_SECS=660` for mesh agents, above
MeshLLM's 600-second backend timeout;
- makes `desktop-tauri-clippy` lint both default and `mesh-llm` cfg
graphs;
- runs the feature-enabled desktop test suite in CI;
- recommends Qwen3.8 27B Q4_K_M for 64 GB-and-larger machines, then
ladders down through Gemma 4 E4B and Qwen 9B for smaller machines;
- keeps stored shared-compute `auto` translated to MeshLLM's supported
wire model `mesh`.

RC8 verification:
- `just ci` passed locally at
`92ecc7ec933bdd4df804cc9f28a2b51efa5313c5`.
- Pre-push differential gates passed, including both desktop Tauri cfg
graphs and package tests.
- A prior isolated runtime smoke used the RC8 binary's OpenAI endpoint
for a Buzz-shaped system/user/tool/tool-result/final-response loop; all
assertions passed and the isolated process was shut down.

Perf previously measured on M5 Metal, Qwen3.8-27B-Q4_K_M: TTFT 0.22–0.32
s, ~25 tok/s streaming; agent-shaped turns ~1 s to first token after the
first (prefix cache).

---------

Signed-off-by: Michael Neale <michael.neale@gmail.com>
Signed-off-by: Jimmy <1fe240cd1a8cf775f6f3060f115e5a303181f3abf28ad4cb0c2515f4a02b36a8@meshllm.communities.buzz.xyz>
Signed-off-by: Alessandro Joabar <sandro@squareup.com>
Co-authored-by: Michael Neale <michael.neale@gmail.com>
Co-authored-by: Jimmy <1fe240cd1a8cf775f6f3060f115e5a303181f3abf28ad4cb0c2515f4a02b36a8@meshllm.communities.buzz.xyz>
Co-authored-by: Mic Neale <305999590+micspiral@users.noreply.github.com>
Co-authored-by: Alessandro Joabar <sandro@squareup.com>
2026-09-08 07:30:45 -07:00
Bradley Axen
cd54e26823 fix(agent): route GPT-5+ model-service FQNs to Responses (#7358)
## Summary
Route Databricks Unity Catalog model services to OpenAI Responses when
their service name matches GPT-5 or newer. These models can reject tools
plus reasoning on Chat Completions.

Match only the service component, using the existing family-token
boundaries and a numeric major version. Catalog and schema names cannot
select the protocol. Keep neutral effort capabilities and the full model
ID unchanged; other services still use MLflow Chat Completions.

Keep the Rust and desktop resolvers in sync, add shared boundary cases
and a captured-HTTP regression for completion and summarization, and
update the documented FQN rule.

### Related issue
No duplicate found in searches for “FQN responses” PRs or “astra”
issues. Related: #6918 introduced Unity Catalog discovery.

Originating conversation:
buzz://message?channel=0b881928-a3a6-4c01-b981-8e64268f01ce&id=770949343bc96a9ed88acd90a1b37d358a0efc52c79237d0fdb491ce02b8d4ed

### Testing
No live Databricks inference test. The gateway must accept the full
model-service ID on its OpenAI Responses route; this remains the
integration risk.

The local `just ci` attempt exceeded its five-minute deadline during
`mobile-check`, so the full repository gate was not completed. All
push-hook checks passed.

Generated with Codex

Signed-off-by: Fizz <400e8babadcee6a7f420103f10a2849d84c4a9c71d5bd04f3948c814216648a3@buzz.block.builderlab.xyz>
Co-authored-by: Fizz <400e8babadcee6a7f420103f10a2849d84c4a9c71d5bd04f3948c814216648a3@buzz.block.builderlab.xyz>
2026-09-08 08:23:48 -06:00
Salman Mohammed
86c189e857 fix(buzz-acp): wake held ACP threads and fence forked sessions (#7340)
## Summary

Adds an independent deadline wakeup so held thread work dispatches after
its 10-second bound even when the relay loop is otherwise quiet. Fences
session ownership by generation so a worker returning after a fork
cannot make an older provider session claimable again.

This follows up on the two post-merge findings from
[#7337](https://github.com/block/buzz/pull/7337#pullrequestreview-5116329341).

### Related issue

Follow-up to #7337.

### Testing

- `cargo test -p buzz-acp`
- `cargo clippy -p buzz-acp --all-targets -- -D warnings`
- Pre-push file-size, differential Rust test, and desktop Tauri gates

No UI changes.

---
**Update Sep 4, 15:35:** Addressed both Codex review findings.
- Queue-cap eviction now prunes orphaned hold deadlines.
- An expired hold stays expired until a worker is successfully claimed.
- Hold timers remain disabled while every worker is busy; worker return
wakes dispatch directly.
- Added regressions for queue eviction and pool exhaustion.

Generated with Codex

---------

Signed-off-by: Salman Mohammed <smohammed@squareup.com>
2026-09-08 10:10:38 -04:00
TolgaCinisli
fa1b27bcc8 fix(mobile): style inline code with the app mono face (#6631)
## Summary

Inline code on mobile renders as **bold body text on a faint background
wash** — no monospace face, no chip, and it cannot wrap. #5257 diagnosed
this as a missing `highlightBuilder`.

That is no longer the right fix. `gpt_markdown` 1.2.0 deprecates
`highlightBuilder` (removal in 2.0.0), renders inline code as a real
chip, and adds `InlineCodeStyle` for restyling it. The package author
confirmed this on the issue. So this PR is an upgrade — 1.1.6 → 1.2.1 —
plus one theme declaration, rather than the builder the issue originally
asked for.

**Where the style is declared.** `GptMarkdownThemeData` goes in
`AppTheme._buildTheme`, which both `light()` and `dark()` call. That
reaches all four `GptMarkdown` call sites — `message_content`,
`transcript_item_widget`, `token_pill`, `custom_emoji_render` — so the
style is stated once instead of per widget. A widget-level
`inlineCodeStyle` would have covered channel messages only, leaving the
other three on the package's defaults.

**What is declared.** Face, size, ink, chip fill and outline — not the
face alone. A face name on its own leaves the rest on the package's
defaults, which put inline code at 14.1sp beside a fenced block's 13, on
a neutral `onSurface` tint rather than the app's code surface. In dark
that tint is *lighter* than the surface, while every other code surface
in the app is recessed, so the chip read as a different kind of object.
All of it now comes from one `CodeStyle` declaration that the fenced
block reads from too, so the two cannot be edited apart.

**Three adaptations the upgrade requires.** Each was found by running
the gate, not by reading the changelog:

1. **`imageBuilder` widened** to `(context, url, width, height)`. This
is a hard compile error, and it is **not listed in the package's
migration guide**, which states "nothing here stops code compiling".
Worth reporting upstream.
2. **`autolink` now defaults to `true`.** `normalizeBareLinks()` already
rewrites bare URLs into Markdown links before rendering, so both would
run. `message_content` opts out with `autolink: false` to keep current
behaviour exactly. The migration guide argues for dropping the
pre-processor instead — a better fix, but a behavioural change that
belongs in its own PR.
3. **`gpt_markdown.dart` now re-exports `markdown_config.dart`**, making
two direct imports redundant. `flutter analyze` reports `No issues
found!` on 1.1.6 and flags both on 1.2.1, so these warnings are new, not
pre-existing.

**Deliberately out of scope.** The three non-message call sites now
autolink bare URLs, since only `message_content` has a pre-processor to
collide with. Custom inline components (`_MentionMd`, `CustomEmojiMd`,
`_ChannelLinkMd`) could additionally declare `allScopesExceptLinkLabel`
— 1.2.0 offers it as the fix for a `WidgetSpan` chip going blank inside
a link label on iOS — but current behaviour is unchanged without it, so
that stays a separate change.

### Related issue

Fixes #5257

Duplicate scan: searched `gpt_markdown`, `inline code mobile`,
`highlightBuilder` and `InlineCodeStyle` across both PRs and issues. No
open PR touches inline code styling. #6135 (link labels) and #6166 (text
selection) also touch mobile Markdown but address different defects.

### Testing

Full gate, `just ci` — exit 0:

| Stage | Result |
|---|---|
| Rust (33 suites) | 4768 passed, 0 failed |
| Desktop | 5799 passed, 0 failed |
| Mobile | **2011 passed**, 0 failed |
| `flutter analyze` | `No issues found!` |
| Desktop + web build | ok |

Run on the branch with `main` merged in, so these numbers match what CI
builds.

**New regression test** — `renders inline code in the app code style`.
It resolves the `CodeTextSpan` the package tags inline code with, which
carries both the resolved `TextStyle` and the colours the chip behind it
is painted with, so face, size, ink, fill and outline are all asserted
rather than a widget's presence. It is negative-controlled: reverting
only the theme declaration fails it with

```text
Expected: a numeric value within <0.001> of <13.0>
  Actual: <14.1>
```

and dropping the declaration entirely falls back to
`packages/gpt_markdown/JetBrainsMono` — so the test measures the real
thing, and it would catch a future regression that silently drops the
theme extension.

The test passes `baseStyle: messageBodyTextStyle`, the style the message
surfaces actually use; the widget's own fallback is the smaller
`bodyMedium`, which would move the expected size.

The test finds paragraphs with `find.byWidgetPredicate((widget) =>
widget is RichText)`, not `find.byType(RichText)`: inline code renders
through `BidiRichText`, a `RichText` subclass, and `byType` matches
exact runtime types.

That is a hazard for any test that reads text back out of a paragraph,
and one landed after this branch was cut:
`message_content_custom_emoji_test.dart` arrived with #6996 and its
`code keeps literal emoji while adjacent known tokens render` case reads
a code span through `find.byType(RichText)`. It passes on `main` and
fails on the merge result, which is what CI builds, so it went red only
once CI was authorized. It now uses the same predicate. The two other
`byType(RichText)` call sites — the rest of that file and
`message_author_meta_test.dart` — were re-run and pass: their content
carries no code span, so the exact type still matches. They were left
alone.

### Screenshots

Rendered through the real `MessageContent` widget with the app's own
fonts loaded, at 390pt wide, 3x DPR. Sample text: ``Set `BUZZ_RELAY_URL`
before launch, then run `just mobile-test` to verify.``

| | Before (1.1.6) | After (1.2.1) |
|---|---|---|
| Light |
![before-inline-code-light](https://raw.githubusercontent.com/TolgaCinisli/buzz/2d2d846291416d9b32d3fb9cfead950bcc4fe123/pr-6631--before-inline-code-light.png)
|
![after-inline-code-light](https://raw.githubusercontent.com/TolgaCinisli/buzz/f230b95c7260a32bd5d76b1ac42130720a168521/pr-6631--after-inline-code-light.png)
|
| Dark |
![before-inline-code-dark](https://raw.githubusercontent.com/TolgaCinisli/buzz/2d2d846291416d9b32d3fb9cfead950bcc4fe123/pr-6631--before-inline-code-dark.png)
|
![after-inline-code-dark](https://raw.githubusercontent.com/TolgaCinisli/buzz/f230b95c7260a32bd5d76b1ac42130720a168521/pr-6631--after-inline-code-dark.png)
|

Before: bold Inter on a flat wash, no chip edge, and `just mobile-test`
breaks across the line with the wash simply ending. After: Geist Mono in
a bordered, rounded chip, and the wrapped fragment gets its own chip on
each line.

---------

Signed-off-by: Tolga Cinisli <tolgacinisli@gmail.com>
Co-authored-by: Tolga Cinisli <tolgacinisli@gmail.com>
2026-09-08 08:05:11 -06:00
Wes
3c7f288c60 chore(release): release Buzz Desktop version 0.5.23 (#7381)
## Buzz Desktop release v0.5.23

- **Frozen main:** `dad5a33865fc81a2e55b3b60746632f615ec1e3a`
- **Reviewed candidate:** `b9392d9d78744df365f9276e1ffe8c1baa5ea903`
- **Previous desktop release:** `desktop-v0.5.22`
- **Proposed immutable tag:** `desktop-v0.5.23`

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

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

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
2026-09-05 18:06:14 +00:00
Jeff Hedlund
dad5a33865 fix(desktop): keep packaged frontendDist relative so Windows embeds assets (#7177)
## Root cause

`tauri-command.mjs` points `frontendDist` at a `mkdtemp` directory so
concurrent OSS/internal packages cannot overwrite each other's assets.
On Windows that is an absolute path with a drive letter.

`FrontendDist` is an untagged serde enum whose **first** variant is
`Url(Url)`, and `C:\Users\...` is a valid WHATWG URL with scheme `c:`,
so serde selects `Url`. `tauri-codegen` then does:

    FrontendDist::Url(_url) => Default::default(),   // embed nothing

A missing *directory* panics with a clear message; a URL is silent. The
build exits 0 and produces an installable app with no frontend assets,
which boots to `ERR_FILE_NOT_FOUND` in the WebView.

Linux and macOS are unaffected — `/tmp/...` has no scheme, so it falls
through to `Directory`.

This affects every Windows build that goes through `pnpm tauri build`,
including `release.yml`'s NSIS job and `windows-canary.yml`.

## Fix

Pass the path relative to the config's own directory. `tauri-codegen`
resolves `frontendDist` with `config_parent.join(path)`, so a relative
path reaches the same directory and cannot parse as a URL. When the temp
directory is on another drive there is no relative form, so the scratch
root is created beside the config instead.

`BUZZ_PROTECTED_BUILD_OUTPUT` still receives the absolute path, and
cleanup is unchanged.

## Testing

`tauriCommand.test.mjs` asserted against the value it had just been
handed, so it could not observe this. Its fake CLI also resolved
`frontendDist` against the process cwd, which is not what Tauri does.

- Fake CLI now resolves against the config directory, matching
`config_parent.join(path)`.
- New case asserts the packaged `frontendDist` is not absolute and does
not parse as a URL. The absolute check is what fails on Linux/macOS, so
the regression stays covered on every platform.
- Verified the new case fails on the unpatched wrapper and passes with
the fix; the two existing cases pass either way.
- Desktop suite: 5844 passed. `useDocumentVisible` has a pre-existing
load-dependent flake that also reproduces on an unmodified checkout.
- Biome check clean on both files.

Verified end to end by rebuilding the Windows NSIS installer: embedded
asset keys in `buzz-desktop.exe` went from 0 to 490, and the app
launches.

---------

Signed-off-by: Jeff Hedlund <jhedlund@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 09:04:06 -06:00
Taylor Ho
f038cbbb0d fix(sidebar): simplify unread indicators and emphasize priority activity (#7134)
**Category:** fix
**User Impact:** The sidebar now presents unread activity consistently:
unread rooms are bold, offscreen activity is counted by destination, and
DMs or directed activity receive stronger emphasis.

**Problem:** Sidebar unread state was split across competing signals:
agent work could trigger overflow, message totals inflated its count,
non-DM rows showed redundant numerals, and routine room activity looked
as urgent as DMs or directed messages. This made the sidebar noisy and
made the overflow value harder to interpret.

**Solution:** Use one stable offscreen unread control that counts rooms
and DMs—not messages—and remove agent work from that signal. Keep
ordinary room activity quiet; promote DMs, mentions, broadcasts, and
relevant thread replies; preserve DM avatars and targeting; bold every
unread room; and retain thread dots while removing non-DM numerals.

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

**desktop/src-tauri/src/unread_catch_up.rs**
Classifies relevant thread replies as priority activity during native
unread catch-up so startup state matches live rendering.

**desktop/src/app/AppShell.tsx**
Passes the priority unread destination set into the sidebar.

**desktop/src/features/channels/useUnreadChannels.ts**
Projects unread destinations, priority state, and thread replies
consistently while keeping Dock badge behavior separate from sidebar
emphasis.


**desktop/src/features/sidebar/lib/useOffscreenActivityChannelIds.test.mjs**
Removes tests for the superseded agent/activity overflow projection.

**desktop/src/features/sidebar/lib/useOffscreenActivityChannelIds.ts**
Removes the old agent-plus-message overflow projection so agent work
alone no longer creates the unread indicator.

**desktop/src/features/sidebar/lib/useSidebarActivityOverflow.ts**
Replaces activity-volume overflow state with unread-destination overflow
state.

**desktop/src/features/sidebar/lib/useSidebarUnreadOverflow.test.mjs**
Covers priority detection and destination-count labels for the new
overflow projection.

**desktop/src/features/sidebar/lib/useSidebarUnreadOverflow.ts**
Counts distinct offscreen unread destinations and determines quiet
versus primary treatment per direction.

**desktop/src/features/sidebar/ui/AppSidebar.tsx**
Renders one stable overflow control, preserves protected-DM visibility
filtering, and prioritizes visible unread DMs for previews and
navigation.

**desktop/src/features/sidebar/ui/AppSidebar.types.ts**
Adds the priority unread destination set to the sidebar contract.

**desktop/src/features/sidebar/ui/CustomChannelSection.tsx**
Stops forwarding non-DM unread counts into custom channel rows.

**desktop/src/features/sidebar/ui/MoreUnreadButton.test.mjs**
Updates control coverage for explicit emphasis, destination labels, and
DM targeting.

**desktop/src/features/sidebar/ui/MoreUnreadButton.tsx**
Applies quiet or primary treatment without changing geometry,
accessibility text, DM avatars, or click behavior.

**desktop/src/features/sidebar/ui/SidebarSection.tsx**
Removes non-DM row numerals while retaining unread weight and thread
preview affordances.

**desktop/src/shared/ui/UnreadPill.tsx**
Shares one composition between quiet and primary states so only color
treatment changes.

**desktop/tests/e2e/badge.spec.ts**
Covers destination counting, promotion without count inflation,
DM/thread priority, row bolding, and removed numerals; also captures the
reviewed UI states.

**desktop/tests/e2e/channel-activity-popover.spec.ts**
Updates channel activity assertions for the numeral-free row treatment.

**desktop/tests/e2e/channels.spec.ts**
Updates channel unread expectations to use bold text rather than a row
count.

**desktop/tests/e2e/thread-unread.spec.ts**
Keeps thread unread-dot and popover coverage while asserting the room
itself is bold.

</details>

## Reproduction steps

1. Open a workspace with enough sidebar destinations to scroll rooms
above or below the viewport.
2. Receive ordinary unread activity in an offscreen room; verify one
quiet `N unread` indicator appears and counts the room once regardless
of message volume.
3. Receive a mention, broadcast, or relevant thread reply in an
offscreen room; verify the same count becomes primary without changing
its geometry or adding a badge.
4. Receive an unread DM, including thread-only activity; verify the
indicator is primary, shows the DM avatar when available, and navigates
to that DM.
5. Scroll the destination onscreen or mark it read; verify the count and
treatment update from the remaining offscreen destinations.
6. Inspect unread non-DM rows; verify their names are bold, numeric
badges are absent, and thread dots still open the unread-thread preview.

## Screenshots

### Ordinary unread room
Routine offscreen room activity uses the quiet treatment.

![Quiet unread overflow
indicator](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/7134/sidebar-unread-overflow-default.png)

### Directed unread activity
The same destination count becomes primary when an offscreen room has
directed activity; geometry and label remain unchanged.

![Primary unread overflow
indicator](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/7134/sidebar-unread-overflow-primary.png)

### Thread-only unread DM
A DM stays primary and retains its avatar even when only its thread has
unread activity.

![Primary DM thread overflow
indicator](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/7134/sidebar-dm-thread-overflow-primary.png)

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
2026-09-04 23:03:41 +00:00
Jordan Mecom
4d447b9c20 Add generic information-flow control core (#7293)
Adds a dependency-free `ifc-core` crate implementing reader-set
confidentiality labels and monotonic per-computation flow state.

- Defines flow ordering, join, and meet over caller-supplied principal
universes.
- Fails closed for unknown or cross-universe input and checks reader
widening at egress.
- Includes property tests for lattice laws and monotonic taint, plus the
design paper that motivates the broker integration.

The crate deliberately contains no Buzz, Nostr, channel, membership, or
grant policy; those remain in the Buzz adapter.

---------

Signed-off-by: Jordan Mecom <jm@squareup.com>
2026-09-04 22:26:44 +00:00
Salman Mohammed
4beffef697 feat(buzz-acp): update base prompt; add buzz context and skills to Pi agents (#7335)
## Summary

- launch Pi through a private Buzz wrapper so managed Pi agents receive
Buzz's base prompt plus the configured thread/channel session model via
`--system-prompt`
- load the Buzz workspace skill directory with Pi's repeatable `--skill`
flag, making `~/.buzz/.agents/skills/buzz-cli` available alongside Pi's
normal global skill discovery
- report Pi and `pi-acp` installation states separately so setup
guidance points to the missing component

`pi-acp` does not currently consume the ACP `session/new` system prompt,
but its normal new-session and restore paths do honor
`PI_ACP_PI_COMMAND` as the executable used to launch Pi. Buzz reserves
that variable and sets it to a private generated launcher. The launcher
invokes `pi` from Buzz's effective `PATH`, adds `--system-prompt <file>`
and `--skill <workspace>/.agents/skills`, and forwards `pi-acp`'s RPC
and session arguments unchanged. This keeps the integration entirely in
`block/buzz`, without changes to Pi or `pi-acp`.

### How PI_ACP_PI_COMMAND works in `pi-acp`

`PI_ACP_PI_COMMAND` selects one executable; it does not accept
arguments. Therefore:

```bash
# Does not work
PI_ACP_PI_COMMAND="pi --skill ~/.buzz/.agents/skills"
```

Buzz does not expose this variable as user configuration. It creates a
private launcher and sets `PI_ACP_PI_COMMAND` only on the `pi-acp`
child. Conceptually, that launcher executes:

```sh
#!/bin/sh
exec pi \
  --system-prompt "$PRIVATE_SYSTEM_PROMPT" \
  --skill "$HOME/.buzz/.agents/skills" \
  "$@"
```

### Related issue

Follow-up to #7208. No duplicate issue or open PR found.

### Testing

- `just ci`
- `cargo test -p buzz-acp pi_launcher`
- `cargo test --manifest-path desktop/src-tauri/Cargo.toml
managed_agents::env_vars::tests`
- `cargo test --manifest-path desktop/src-tauri/Cargo.toml
managed_agents::discovery::presets::tests`
- pre-push branch checks

No screenshots: the UI change is state-dependent setup guidance only.

---
**Update Sep 4, 13:35:** Made `PI_ACP_PI_COMMAND` entirely Buzz-owned.
- Managed agent configuration now rejects the variable as a user
override.
- Buzz always launches `pi` from its effective `PATH` and rejects
inherited values before creating its private launcher.
- Removed the custom Pi executable discovery path and its internal
environment alias.

---
**Update Sep 4, 14:03:** Reject inherited `PI_ACP_PI_COMMAND` values
instead of replacing them.
- Pi startup now fails with an actionable message telling the user to
unset the variable.
- Removed the Pi-specific exception from the generic ACP environment
injection path.

Generated with Codex

---------

Signed-off-by: Salman Mohammed <smohammed@squareup.com>
2026-09-04 21:17:00 +00:00
Logan Johnson
4afef8649a fix(desktop): restore mention chip identity icons (#7338)
## Summary

Restore the missing **@ glyph for people and robot icon for agents**
after #7133, and incorporate the requested compact public-key display.

- Wrapping mention chips render their existing bounded icon-bearing
leading fragment.
- Readonly chips show bound keys using the same `8 leading…4 trailing`
formatter as the channel member list: `Scout (150b20bd…15dc)`.
- Full literal labels and exact keys remain authoritative in metadata,
profile targets, title/accessible-name attributes, editor text, saved
bodies and recipient tags. Display abbreviations are never used for
recipient lookup.
- Copy/paste restores the full literal label for a complete compact
chip; partial selections remain plain text. Two keys sharing the same
abbreviation still round-trip to their separate exact recipients.
- No recipient-resolution, authorization, wire-format, composer, or CSS
changes. Existing labels, icons, cloud markers and ordinary mentions
remain intact.

## Verification

Published candidate `9365ab9bc9d9c5d10802580cd576f8e378ff492f`, based on
main `e09f715c9d0ee2cb7bf8a39061e601f3a502f588`:

- **6,444 desktop unit tests passed** on this candidate's final source
tree.
- **44 mock-Chromium tests passed, zero retries in the final run**
across mention recipients, clipboard and cloud provenance: exact
recipient selection, ambiguity rejection, send/edit/reopen, forwarding,
full/partial copy, mismatched-key rejection, matching-abbreviation
collisions and 100%/150% narrow-window geometry.
- TypeScript, desktop Biome/check guards, protected-feature production
build and E2E build passed.
- The compact-renderer regression fails with the formatting call
removed. Original missing-icon and hidden-text accessibility regressions
have red/green evidence.
- Fresh self-review traced rendering, full-key metadata, copy
classifier, paste normalization and identity trust. The same display
formatter owns the accepted compact form on both clipboard sides.

Iteration exposed an existing team-insertion separator flake (passed
final full run) and two new fixture assumptions: non-member sends
require invitation, and Chromium rich paste may retain an NBSP
separator. Tests now exercise invitation and normalize only that
separator when comparing the captured full body and exact tags; no
product change was needed for either.

Earlier local repository-wide `just ci` completed in two invocations
because its initial call hit the ten-minute tool limit during Tauri
compilation. Unchanged native/mobile/backend evidence is reused; the
desktop delta received the full checks above and new remote CI. Native
VoiceOver, real Tauri selection, dark theme and non-Chromium observation
were not performed. Browser artifacts exercise real frontend with mocked
Tauri/relay boundaries, not an installed release.

## Review and visual evidence

Current-head CI and automated review must complete after this update;
the old `2997bfb5` green results do not establish this new head.
Required human review remains separate from agent approvals. No
merge/install/restart authorization.

Before/after icon evidence:
https://github.com/block/buzz/pull/7338#issuecomment-5544575866

Updated compact-key screenshots are posted below. The editor
intentionally retains the full literal address; only readonly chip
display is abbreviated.

Origin:
buzz://message?channel=3355d33a-b72a-423a-b064-a58275f9a8af&id=38b3a27e689f5a9604e273d45f4e3122fceba76081e7bcd3bbdbf439524a5a18

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
2026-09-04 13:36:17 -07:00
morgmart
5d107836c6 Persist video playback speed preference (#7336)
## Summary

- persist the selected desktop video playback speed as a device-level
preference
- apply the shared preference to inline and review video players,
including after a reload
- cover persistence, validation, cross-window updates, and the
end-to-end playback flow

## Verification

- `node --test
desktop/src/shared/lib/videoPlaybackSpeedPreference.test.mjs`
- `cd desktop && pnpm exec tsc --noEmit`
- `cd desktop && pnpm check:px-text`
- `cd desktop && pnpm test`
- `cd desktop && pnpm build:e2e && pnpm exec playwright test
--project=smoke video-attachment.spec.ts`
- pre-push: desktop check, file-size check, typecheck, and 6,416 desktop
tests

---------

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
2026-09-04 13:10:17 -07:00
Jordan Mecom
e09f715c9d Verify ACP relay events before prompt routing (#7010)
## What changed

Verify every Nostr EVENT received by buzz-acp immediately after
deserialization. Events with an invalid NIP-01 ID or Schnorr signature
are dropped before subscription routing, deduplication, replay-watermark
updates, membership handling, or either harness queue.

## Safety

Signature verification runs on the blocking pool so cryptographic work
does not block the relay task. A verification failure drops only that
event and keeps the connection available for subsequent valid traffic.
The existing observer-control verification remains as defense in depth.

Regression tests cover valid delivery; changed content, ID, signature,
pubkey, tags, and timestamp; a forged owner shutdown command with a
recomputed ID; forged membership notifications; and forged
observer-control events.

## Testing

cargo test -p buzz-acp

cargo clippy -p buzz-acp --all-targets -- -D warnings

just ci

---------

Signed-off-by: Jordan Mecom <jm@squareup.com>
2026-09-04 11:19:03 -07:00
Will Pfleger
b17c0776b7 fix(buzz-acp): bound busy-owner hold to prevent cross-channel starvation (#7337)
## Problem

#6732 added a busy-owner hold to the ACP harness: when a scope's
recorded session owner (`session_owners`) is checked out on **any**
turn, `dispatch_pending` holds the scope's batch instead of dispatching
it. The hold was added to keep one provider session per thread — but it
is unconditional: it applies to `Conversation` scopes too, and it has no
time bound.

Under the default `session_policy=channel`, every channel collapses to a
single `Conversation` scope, so once two channels' sessions land on the
same worker (pass 2 of `try_claim` picks the first idle worker by index,
so this happens quickly after any restart), channel A's mention starves
behind channel B's in-flight turn — for up to the full
`max_turn_duration` (7200s by default) — while other workers sit idle.
The only signal is a DEBUG-level log, and the 👀 seen-reaction is added
at queue admission *before* the hold decision, so the user sees the
agent acknowledge the mention and then nothing.

Observed in production on the first day of the v0.5.22 rollout: three
separate incidents where a mention got 👀 but no turn started until an
unrelated channel's turn ended on the shared worker (in the worst case
the blocking turn sat in a single tool call for 6+ minutes).

## Fix

One new seam, `AgentPool::hold_decision`, replaces the raw
`should_hold_for_busy_owner` check in `dispatch_pending` (the predicate
itself is unchanged and remains the inner check):

- **`Conversation` scopes never hold.** Channel-policy channels and all
DMs dispatch immediately; a busy owner means forking onto an idle
worker, exactly the pre-#6732 behavior. This removes the cross-channel
head-of-line blocking entirely for the default policy.
- **`Thread` scopes hold for a bounded window.**
`HOLD_BUSY_OWNER_TIMEOUT` (10s) is measured from the first time the
batch is held (`held_since` stamp); once elapsed, the batch stops
holding and forks a fresh session on an idle worker, rebuilding thread
context from the relay. This preserves #6732's session-continuity intent
for the momentary-busy case while capping the worst-case wait. No new
timer is needed: held batches are requeued with preserved timestamps and
re-evaluated on every dispatch trigger (turn end, relay event, 30s
maintenance tick), so the effective worst-case re-check gap on a fully
silent system is one maintenance tick.
- **Holds are observable.** Holding logs at INFO and a hold expiry logs
at WARN (previously DEBUG-only), and both emit observer-feed events
(`busy_owner_hold`, `busy_owner_hold_forked`) with the scope, owner
index, and held duration.

`held_since` is derived state and is cleared on every removal path:
dispatch/fork (inside `hold_decision`), `invalidate_channel_sessions`,
`invalidate_scope_session`, and `switch_idle_agent_model`.

## Accepted trade-offs

- A fork after an expired hold leaves the old owner's now-orphaned
thread session in its session map until natural rotation/invalidation —
benign, and identical to pre-#6732 fork semantics (`loadSession: false`;
sessions are worker-pinned, so migration is not an option).
- Under sustained pool exhaustion the hold stamp is cleared on the fork
attempt and re-stamped next cycle, so the bound is effectively "timeout
after a worker frees up," not absolute wall clock.

## Tests

- New table test `hold_decision_covers_variant_session_busy_and_timeout`
over the full input space (scope variant × idle-session presence × owner
busyness × elapsed vs. window). The `Conversation` + busy-owner row is
the cross-channel regression guard; the past-window row guards the
bound. Both were mutation-checked: removing the variant gate or the
timeout branch fails the suite.
- `busy_session_owner_holds_batch_instead_of_forking_session` extended
with the Hold → ForkAfterHold transition, the `Conversation` dispatch
guard, and `held_since` pruning on channel invalidation.
- Scope-invalidation and idle-model-switch tests extended to cover
`held_since` cleanup alongside the existing `session_owners` assertions.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-09-04 14:07:45 -04:00
Logan Johnson
e7e29937a1 feat(desktop): invite owned agents from standalone forums (#7125)
## Summary

In Desktop's standalone Forums, selecting an owned agent from another
device could leave a post or reply unsendable if the agent had not
joined the forum. This adds **Invite / Cancel** to the send flow so you
can resolve membership without leaving your draft.

- **Invite** checks response policy and your permission to add members,
adds the agent to the forum, waits for refreshed membership, then
rechecks authorization before posting to the original destination.
Membership is forum-wide, not limited to one post.
- **Cancel / Escape** keeps the text, attachments and selected
recipients for retry. Unlike chat's **Do nothing / Send anyway**, this
dialog has no reference-only send choice. Invite is disabled while
pending; Cancel remains available.
- Leaving the source post/reply cancels its pending invitation, even if
you return. Errors remain visible, focus returns to the initiating
editor when appropriate, and late completion cannot resume a cancelled
post or interfere with a newer attempt.
- A rejected send restores text, uploaded media and exact selected
recipients to the source draft only if no newer edit, deletion, upload
intent or send supersedes it. Clipboard verification settles before
recipient capture, with stale edits/navigation fenced out.

### Related issue

Targets `main` after #7124 merged. This PR reuses its publication checks
and draft protection; the five forum commits have been replayed
unchanged onto the merged parent. Owned-agent discovery (#7122) is
already merged. Split from #7114.

Forum creation/templates, channel-less Notes and local-agent management
are unchanged. Duplicate-name binding from #7133 is already merged and
retained by this stack. Inviting does not start a remote agent or
promise that it is online or will reply.

### Testing

Forum composer lifecycle tests, including clipboard-settlement cases,
and TypeScript/changed-file formatting checks passed after the restack.
Earlier invitation, focus and transport-recovery browser coverage is
retained, not claimed as a fresh full browser run on this head. See
[live CI](https://github.com/block/buzz/pull/7125/checks) for
current-head results. Browser evidence uses mock IPC; no full local
`just ci` pass or native/live-relay validation is claimed.

To try it: open a forum post or reply, select an owned nonmember agent
and send. Cancel, then retry without reselecting; Invite should add that
agent before posting. Deny the add to check the visible error and
retained draft. Navigate away/back during a pending invitation or
rejected send; no stale publication or overwrite of a newer draft should
occur.

![Forum invitation with Invite and
Cancel](https://raw.githubusercontent.com/block/buzz/6481bd088d661975672addcc834231df6ac88705/pr-7125--forum-invite.png)

*Earlier mock-browser capture, not current-head runtime proof. [Error
and successful-post
captures](https://github.com/block/buzz/pull/7125#issuecomment-5513883462);
no before-state/native capture available.*

**Limits:** cancellation cannot undo accepted membership changes or
dispatched posts; authorization and publication are not atomic. Recovery
is same-window, subject to browser storage limits, and is not a durable
in-flight send journal: reload/crash can lose a pending snapshot.
Cross-window coordination and in-flight upload custody are unchanged.
The parent's legacy member-agent compatibility does not establish
ownership for nonmember invitations.

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
2026-09-04 17:33:30 +00:00
Logan Johnson
d595806fc3 fix(desktop): authorize remote mentions at publication (#7124)
🤖

## Summary

In Buzz Desktop, you could own an agent running on another device but be
unable to mention it in a channel where it had not yet joined: it was
filtered out before you could invite it. A selected agent could also
disappear from the message's recipients when permissions changed. This
lets you select an eligible agent in the existing **@ menu**, invite it
from the message composer, and send to that agent—or see an error and
keep your draft rather than silently sending without it.

#### Where the experience changes

| Screen / control | Before → after |
| --- | --- |
| A channel's **Message #…** composer, or a message's **Reply in thread
to …** composer | Type `@` (or use the existing @ button), choose your
agent, write the message and press **Send message**. An owned agent not
yet in the channel can now reach the existing **“Mention people outside
this channel?”** dialog when its response settings allow you to address
it. |
| That dialog's **Invite** button | Previously the membership
requirement could block the agent before the invitation. Now Invite
checks permission to add it, adds it as an agent member of the
**channel** (not just the thread), then rechecks membership and response
permission before sending the waiting message. An agent already in the
channel needs no invitation. |
| Existing direct message, or the new-message screen with the **To:**
field | A mention is checked against the conversation the message will
actually enter, including a newly created direct message, rather than
the old or not-yet-created destination. This does not add an Invite
control to direct messages. |
| Editing a message / sending attachments | The selected agent remains
part of the send or edit attempt through attachment upload and the final
permission check. Lost permission produces a visible error instead of
dropping that recipient. |

**Invite is not the only chat choice.** The existing **Do nothing**
button sends the message *without inviting or notifying the nonmembers*;
their names remain references in the text. Where you cannot invite, that
choice is labelled **Send anyway**. To abandon the send instead, dismiss
the dialog with Escape. Invitation actions are disabled while
preparation is pending, preventing duplicate clicks.

**Leaving and returning must not resurrect a cancelled send.** Switching
threads or leaving the composer cancels its pending invitation, even if
you return to the same thread. Cancellation before dispatch sends no
message; an accepted membership change cannot be automatically undone.
An ordinary send without a pending invitation remains bound to its
original destination rather than following you into another
conversation.

**Failed sends must not overwrite your next draft.** If you leave a
thread, return and replace or deliberately clear its draft while an
older send is pending, the older failure cannot restore deleted text,
recipients or files; success cannot erase the newer draft—even if its
text is identical. An untouched draft cleared automatically for sending
remains recoverable on failure. This protection also covers reopening
the composer and starting a newer send.

The channel timeline also keeps its existing **new-messages / Jump to
latest** button available when newer messages are waiting to be
displayed. For example, after sharing a reply to the channel and closing
the thread panel, you can click the catch-up button to reveal buffered
messages. Closing the thread does **not** guarantee the shared row
appears automatically or force you away from reading history.

### Related issue

Built on [#7122](https://github.com/block/buzz/pull/7122), base branch
`split/owned-agent-discovery`, which lets Desktop find and verify owned
agents independently of this device. Current integration head:
`1144465d00273cf74b7c22544ae5a3299bd98560`, built on exact published
root `3a56d17824522580fe04cae463b54f4c7ba66021`. Root #7122 has its own
CI and security gates; this PR must not land ahead of that dependency.
Finding an agent is not channel membership, online status or a promise
of a reply. This PR changes what the existing message controls can do
with those agents; it adds no profile, presence, cloud marker or remote
start/stop UI.

Standalone forum post/reply **Invite / Cancel** is added separately in
[#7125](https://github.com/block/buzz/pull/7125); here those composers
only gain visible authorization errors. Same-name selection/binding
fixes ([#7133](https://github.com/block/buzz/pull/7133)) and mention
spacing ([#7128](https://github.com/block/buzz/pull/7128)) are not
included.

Extracted from [#7114](https://github.com/block/buzz/pull/7114)
(historical source `98fe33ec`). [Behavior and draft-recovery
contract](1144465d00/docs/remote-mention-routing.md)
· [Originating
discussion](buzz://message?channel=f7a9536a-1738-4bad-a888-b3ea25010ef1&id=7aa1f0ab23dce514bd8a0221441cf005bf428914621171472b79747c50820848).

### Testing

![Channel composer after Send: RemoteScout is not a channel member;
Invite adds it before sending, while Do nothing sends without inviting
or notifying
it](https://raw.githubusercontent.com/block/buzz/b20345da0bd648ecbc78988637c89893a33ca2f3/pr-7124--remote-invite.png)

*Earlier candidate, mock desktop browser: the existing channel dialog
now reachable for an eligible owned agent on another device. The two
buttons have different send outcomes; Do nothing is not Cancel.*
[Success and denial
captures](https://github.com/block/buzz/pull/7124#issuecomment-5482292703)
· [Pending-state
capture](https://github.com/block/buzz/pull/7124#issuecomment-5482604329).
These show the relevant UI, not live agent availability, native
authorization or the later draft-storage/catch-up repairs. No
before-state screenshot is available.

Existing coverage exercises exact recipients, invitation
rejection/cancellation, new direct-message destinations, uploads, edits,
thread re-entry and stored-draft deletion. The timeline regression
checks the shared reply becomes visible using the available catch-up
action.

**Integration validation (2026-09-02):** independently reviewed the
routing delta onto root `3a56d178`: seven original patches unchanged;
two reconciliations retain generic publication-error toasts alongside
authorization errors and retain non-authored editability updates. Added
two production-hook regression tests (normal and queued-media
publication) requiring visible generic error, recovered draft and
released pending state.

- Writer validation: **5,995 Desktop tests**, **42 focused tests**, **22
mock-IPC browser journeys** (18 routing, 2 root provenance, 2
destination binding), and **1 voice-note failure journey** passed; lint,
types, size guards and E2E build also passed.
- The broad suite ran before the final formatting-only test amendment,
not as an exact-final-head rerun. Independent AST comparison confirmed
that amendment is semantics-preserving; **4 fresh assertions at final
`1144465d`** passed. Publication rechecked final-head TypeScript,
amended-test formatting and `git diff --check` successfully. No new full
repository `just ci` run is claimed.
- Browser tests use an isolated E2E build and **mock IPC**, not live
relay/native authorization. Historical screenshots above are explicitly
earlier UI evidence, not exact-head runtime certification. Packaged
Tauri/live-relay behavior was not independently witnessed.
- **Published-head gates:** [current CI
run](https://github.com/block/buzz/actions/runs/33657948560) and
[renewed exact-head formal review
request](https://github.com/block/buzz/pull/7124#issuecomment-5513218340)
must clear before landing. [Earlier CI
run](https://github.com/block/buzz/actions/runs/33438436438) and the two
earlier approvals cover `7ffead0f`, not this new head. Root CI/security
clearance remains separate; the independent scoped integration approval
is not merge authorization.

To try it: in a channel or thread, select an owned nonmember agent,
Send, then Invite or Escape and retry. Deny the add or revoke its
response permission before sending: expect a visible error and
recoverable draft, not a message missing the agent. During a pending
send, return to the source thread, edit or clear the draft, then leave
again: late completion must not overwrite that choice.

**Limits:** permission checks and sending are separate operations;
cancellation cannot retract a dispatched message. Draft protection is
same-window, not new cross-window deletion synchronization. Standalone
forum transport failure can still restore text/media without the exact
selected recipients. Native compatibility is inherited: open-source
builds may still recognize a valid legacy, self-declared agent already
in the channel when verified ownership is absent or rejected; that does
not establish ownership or unlock this owned-nonmember invitation path.
Invalid policy from a verified owner is still rejected. No agent
response is guaranteed.

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
2026-09-04 16:35:35 +00:00
Salman Mohammed
ce9decb235 fix(acp): rename system tag to agent-instructions (#7332)
## Summary

Rename the paired persona prompt boundary from `<system>` to
`<agent-instructions>` in both modern and legacy ACP delivery paths.
Update Desktop diagnostics to label the new boundary as Agent
Instructions while keeping archived `<system>` captures readable.

`system` is confusing (esp with system role) and agent instructions is
what shows up in the UI today

<img width="767" height="298" alt="Screenshot 2026-09-04 at 11 37 54 AM"
src="https://github.com/user-attachments/assets/4e34ac06-db50-461e-a555-ce17885b0031"
/>

### Related issue

Follow-up to #6701. No duplicate open issue or PR found.

### Testing

- `cargo test -p buzz-acp`
- `cargo clippy -p buzz-acp --all-targets -- -D warnings`
- `node --import ./desktop/test-loader.mjs --experimental-strip-types
--test
desktop/src/features/agents/ui/agentSessionTranscriptHelpers.test.mjs
desktop/src/features/agents/ui/agentSessionTranscript.test.mjs`
- `pnpm --dir desktop typecheck`
- Pre-push hook: 6,237 Desktop tests, 3,164 Tauri tests, and all 13 Rust
unit-test lanes passed

Generated with Codex

Signed-off-by: Salman Mohammed <smohammed@squareup.com>
2026-09-04 16:35:18 +00:00
Logan Johnson
ee883d73fb fix(desktop): bind duplicate mention selections to exact recipients (#7133)
## Summary

Selecting two people or agents named Scout could replace the first
recipient with the second even though the message still looked right.
This binds each selection to its exact identity: the first keeps
`@Scout`, and a conflicting selection gets `@Scout (<full public key>)`.
Removing one no longer removes or redirects the other.

- Reuse the existing **@ suggestion list**; team selection and automatic
agent addressing reserve and reuse distinct labels too. Typing an
ambiguous name manually shows an instruction to use the picker and
preserves the draft without publishing—in chat, edits and standalone
forums.
- Share literal mention matching across recipient extraction, removal,
display and editing so a shorter name cannot claim another recipient's
longer or qualified label.
- Rebuild authored references when a message is edited, and use the
latest authorized snapshot when reopening or forwarding.
Rendering/editing can recover qualified identities only from the
message's recorded references, not from a key typed into its body.
Unresolvable historical names remain literal rather than guessed
recipients.
- Wrap full-key labels within narrow/zoomed layouts while keeping their
complete accessible label and ordinary mention icons. Edit activation
waits for the action menu's focus cleanup before focusing the editor.

### Related issue

Targets `main`; mention spacing (#7128) is already merged. Split from
#7114. This is independent of the #7124 → #7125 remote-invitation stack
and does not expand agent eligibility or invitation permissions. The
separate Enter-selection suffix issue remains tracked in #7253.

### Testing

The desktop unit suite and focused mock-Chromium checks passed on the
published integration candidate, including pending-paste selection,
edit/forwarding, copy and narrow-layout cases; formatting, types and
frontend builds passed. On `0b3b18c0`, 64 focused trust/paste/selection
unit tests and six mock-Chromium tests passed with zero browser retries,
including actual timeline chip copy → fresh channel paste → send and
mismatched-key rejection; TypeScript, changed-file Biome and an isolated
E2E build passed. The broader browser run had copy failures before the
focused repairs and is not claimed as wholly green. See [live
CI](https://github.com/block/buzz/pull/7133/checks) for current-head
results. No full local `just ci` pass, native/live-relay or
cross-browser validation is claimed.

To try it: select two same-name recipients, remove one, send, then
edit/reopen and forward; only the intended identities should remain.
Type an ambiguous name without choosing a suggestion and check that
sending retains the draft with an error. Inspect full-key labels in a
narrow window at 150% text size, and open Edit and type immediately.

![Ambiguous mention keeps the chat
draft](https://raw.githubusercontent.com/block/buzz/c48053209b36b3b1bf7197cf2d606d27b48a27c0/pr-7133--ambiguous-general.png)

![Full-key label in a narrow, zoomed
composer](https://raw.githubusercontent.com/block/buzz/ac262f00de6fe3860bff2f87dc0e98165ed6b651/pr-7133--layout-1.5-composer.png)

*Earlier mock-browser captures, not current-head runtime proof. No new
before-state capture; screenshots alone do not prove recipient
delivery.*

**Clipboard trust:** generated full-key-qualified mentions now retain
their exact recipient after copy/paste when the full key matches the
clipboard record and community directory/profile state independently
vouches for the base alias, including numeric collision suffixes. A
qualifier alone does not establish trust. Arbitrary historical labels
still cannot always be reconstructed.

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-authored-by: Bad Janet <150b20bdf6130418df9239dd1bd082c71612c8d653b47c277200365b9be215dc@buzz>
2026-09-04 11:51:41 -04:00
Luke Tornquist
cd02b693aa refactor(relay): extract NIP-29 membership authorization (#7285)
## Why

NIP-29 membership authorization for kinds 9000, 9001, and 9022 was
embedded in a large async handler, requiring Postgres and Redis to test
and repeating the last-owner rule across five call sites. This addresses
[TheSentinel454/buzz#24](https://github.com/TheSentinel454/buzz/issues/24)
without adding the broader state/repository abstraction suggested there
because HTTP and WebSocket writes already share ingestion.

## What

- Extract pure, typed membership authorization decisions while leaving
database reads and mutations in `validate_admin_event`.
- Preserve existing client-visible errors and independent database
last-owner safeguards.
- Collapse five last-owner policy restatements into one predicate and
share the identical self-departure policy.
- Include the relay decision modules in `just test-unit`, so these tests
execute in CI.

## Risk Assessment

Medium-low. This touches production relay authorization, but
intentionally preserves wire behavior and database defense in depth;
exhaustive decision tables and relay-backed tests cover the affected
paths.

## Simplification

This removes repeated policy from the orchestration path and makes the
rule set directly testable without introducing a repository trait or
second transport path.

## Verification

Verified at `15255a090797f85874921120003c645962fefaed`:

- `just test-unit` — all 10 package summaries passed. An initial run hit
two unrelated timing-sensitive `buzz-acp` failures; the complete retry
passed 905/905 in that package.
- `cargo fmt --all -- --check`
- `cargo clippy --workspace --all-targets -- -D warnings`
- `just file-size-check` — 10/10 policy tests passed.
- `just security-review-check` — 13/13 tests passed.
- Push preflight — `push-head-scope`, `branch-skew`, `file-size-check`,
`rust-tests`, and `desktop-tauri-checks` passed.
- Blox Postgres relay lane — 84/84 at the byte-identical source patch;
full live `e2e_relay` behavior matched the clean base (45 pass and the
same pre-existing kind:9002 failure on each).

Generated with Claude Code

---------

Signed-off-by: tornquist <tornquist@squareup.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Codex <noreply@openai.com>
2026-09-04 10:46:55 -04:00
Will Pfleger
01bacb8df3 chore(release): release Buzz Desktop version 0.5.22 (#7308)
## Buzz Desktop release v0.5.22

- **Frozen main:** `75f101d8b4f5b4b26f9891b73b87c67fedac25a0`
- **Reviewed candidate:** `9ceb1f79bbc21785a0a075c40aecb3c058b1ea15`
- **Previous desktop release:** `desktop-v0.5.20`
- **Proposed immutable tag:** `desktop-v0.5.22`

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

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

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
2026-09-04 09:57:17 -04:00
Matt Toohey
b1f6b7ef77 feat(desktop): preserve mentions across copy and paste (#7228)
Copying a message out of the timeline lost the mention. The rendered
chip drops
the `@` for display, so the clipboard carried "John Smith" — two
ordinary words
no composer could bind back to a pubkey. Pasting into another channel
produced
dead text, and sending it tagged nobody.

## What changed

Every Buzz copy now writes two clipboard flavors in one transaction:

- **`text/plain`** — readable anywhere, sigils restored, no pubkeys.
This is what
  TextEdit, Slack, and every other external app receive.
- **`text/html`** — the same content with each mention wrapped in a span
carrying
  `data-mention-pubkey` / `-label` / `-kind`.

On paste, the composer harvests those records, registers each `name →
pubkey`
with the existing mention machinery, and inserts the content: the chip
re-lights
and the send path recovers the identity the author tagged. A marker
attribute
records what the plain flavor holds, so a Markdown copy pastes through
the text
pipeline and a rendered copy through the HTML one.

**Covered surfaces:** timeline selection copy, thread-panel selection
copy,
forum post/reply selection copy, "Copy message", and composer copy/cut —
plus
paste in both the channel and forum composers.

## Trust boundary

Clipboard HTML is untrusted, and the branch treats it that way:

- Records are capped (50), labels bounded (200 chars), and a pubkey must
be
  64 hex before it can become a `p` tag.
- **Only mentions the paste actually shows are registered.** An empty
`<span data-mention-pubkey=… -label="Jane Doe">` would otherwise rebind
that
display name for the rest of the composer session, so a later
hand-written
@Jane Doe would chip-light convincingly against the attacker's key. Each
  branch registers only the records whose label appears in the text *it*
inserts, matched with `getMentionOffsets` — the same matcher the
send-time
  extractor uses.
- **The visibility gate reads only what ProseMirror will insert.**
`DOMParser` hard-drops `script`, `style`, `title`, `noscript`, `object`,
and
`head` content, so `visible<style>@Jane Doe</style>` beside an empty
chip span
used to smuggle a binding past the gate. Those elements are stripped
before
  either output is derived.
- **A partial chip never gains a sigil.** A selection crossing a chip
boundary
falls through to the browser's default copy, which serializes the full
identity
attributes around a slice of the text — pasting that invented "@Smith"
out of
"John Smith". Paste now leaves a fragment as plain text, tolerating only
what a
whole chip picks up in transit (restored sigil, author casing, U+00A0
swaps,
  and the label cap's own ellipsis).

Both clipboard sides share one `matchChipTextToLabel` helper, in the
module that
owns the label attributes, so copy and paste cannot drift on what counts
as a
whole chip.

## Notes

- Mention matching reuses `getMentionOffsets`, so code spans and fences
are
  excluded and the longest display name wins.
- The plain flavor inlines chip boxes before reading `innerText`; a chip
is a
flex container, so the browser's own copy split "@John Smith" onto its
own line.
- `MarkdownMention` and `MacEmacsTextShortcuts` are extracted verbatim
from
`markdown.tsx` and `useRichTextEditor.ts` to keep both files under the
size gate.

## Testing

- ~40 unit tests over the flavor builder/parser, the visibility filter,
the
  ignored-tag sweep, and the chip-match verdicts.
- A Playwright spec (`desktop/tests/e2e/mention-clipboard.spec.ts`)
driving real
  copy/cut/paste DOM events: timeline and "Copy message" of a multi-word
non-member mention pasted into another channel and sent with the
original
pubkey in its `p` tag; the forum copy → forum reply round trip; composer
  copy/cut; plain flavor asserted to contain no 64-hex string; the
boundary-crossing drag; and the hidden-record and `<style>` smuggling
vectors.
- Every regression test is bound to a production seam and fails with its
guard
  removed.
- `just ci` / pre-push lanes green (5927 desktop unit tests, typecheck,
lint,
  file-size gate).

---------

Signed-off-by: Matt Toohey <contact@matttoohey.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-09-04 16:45:13 +10:00
Logan Johnson
4b0744d7f3 test(desktop): await Bestie drag and profile hover endpoints (#7294) 2026-09-03 23:52:55 -04:00
Luke Tornquist
2ac0aa1dd1 Collapse contiguous join messages (#7262) 2026-09-03 19:26:21 -04:00
Will Pfleger
75f101d8b4 chore(release): release Buzz Desktop version 0.5.21 (#7301)
## Buzz Desktop release v0.5.21

- **Frozen main:** `88687876f7808a2fd742b7eb2e4b9f87d999ad8d`
- **Reviewed candidate:** `47e4e0347fb4142df55b03e0e3dc537fe33a5d62`
- **Previous desktop release:** `desktop-v0.5.20`
- **Proposed immutable tag:** `desktop-v0.5.21`

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

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

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
2026-09-03 17:41:22 -04:00
Will Pfleger
8e7b275cb0 fix(scripts): copy global-agent-config.json in buzz-adopt-prod-agents (#7303)
## What

Extend `scripts/buzz-adopt-prod-agents.sh` to copy
`agents/global-agent-config.json` from the prod app-data store to dev as
part of the existing atomic bundle commit, and document what the script
cannot copy (baked build-time defaults).

## Why

`global-agent-config.json` holds the owner's global agent defaults
(`env_vars`, `provider`, `model`, `preferred_runtime`). Previously the
script carried the dev copy across verbatim — it was absent from the
bundle drop list and not in `RECORD_FILES`. After a `just reset`, the
dev build inherited stale global defaults (or none at all) instead of
mirroring prod, inconsistent with how all other bundle files are
handled.

The "WHAT THIS DOES NOT DO" header lacked any mention of baked
build-time defaults, which are a separate (and common) source of
prod/dev parity confusion.

## Changes

- **`RECORD_FILES`** — add `global-agent-config.json`. The file has no
volatile fields (`GlobalAgentConfig` struct =
`env_vars`/`provider`/`model`/`preferred_runtime`), so it joins the
verbatim-copy path already used by `teams.json` — no new transform.
- **Stage-seed block** — add `global-agent-config.json` to the `rm -rf`
drop list so the prod overlay replaces it cleanly; remove its stale
mention in the "carried verbatim" comment.
- **Docs/comments** — update "4-file bundle" → "5-file bundle" in the
DMG concurrent-run warning and the step 2 comment; list the file
explicitly in the step 2 header comment.
- **"WHAT THIS DOES NOT DO" header** — add a bullet explaining that
baked build-time agent defaults (`BUZZ_BUILD_BUZZ_AGENT_PROVIDER` /
`BUZZ_BUILD_AGENT_ENV` via `option_env!` in `build.rs`) are compiled
into the binary, not stored in app-data. Includes the correct `just
production` invocation pattern and a `.env` warning.
- **Preflight symlink checks and dry-run output** iterate `RECORD_FILES`
and pick up the new entry automatically — no other changes needed.

Verified: `bash -n` clean, `shellcheck` clean, `--dry-run --force` shows
`[dry-run] stage global-agent-config.json (0600)` in the expected
position.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-09-03 17:32:10 -04:00
ravarora2
88687876f7 feat(relay): add early startup lifecycle logs (#7258)
## Why

Early relay failures can currently appear as a container restart without
a trustworthy in-process account of whether crypto, structured logging,
configuration, relay identity, or the metrics listener failed. Most of
those steps happen before the Prometheus exporter exists, so their
chronology belongs in logs rather than metrics.

Implements the logs-only early-startup slice of #7238. Post-bind
Prometheus exporter supervision is tracked separately in #7284.

## What changed

- create a process lifecycle recorder before the Tokio runtime and emit
a fixed, versioned JSON schema directly to stderr;
- record started and exactly one terminal event for `crypto_init`,
`tracing_init`, `config_load`, `key_load`, `metrics_bind`, and the
aggregate `process_telemetry` phase;
- keep every status and reason bounded and suppress raw errors that
could contain credentials, keys, URLs, or other secrets;
- return typed metrics-install errors so `metrics_bind` can be
classified without logging raw values, while preserving the existing
public `metrics::install` API;
- document the logs-only evidence contract and add real child-process
regressions for success and failure paths.

This PR adds **no startup metric families** and no dashboard contract.
Existing application metrics remain unchanged.

## Verification

Exact head: `8faf7526822a119efa035e58b2b3c59aa67fc81d`

- `cargo fmt --all -- --check`
- `cargo clippy -p buzz-relay --all-targets -- -D warnings`
- relay binary target: 13 passed, 1 PostgreSQL-only test ignored
- real relay child-process lifecycle target: 9 passed
- full relay package library target: 1,023 passed, 89 ignored; the same
six media tests failed at `crates/buzz-relay/src/api/media.rs:1145` with
`Sqlx(PoolTimedOut)` because local PostgreSQL is unavailable
- three independent exact-head reviews found no correctness, security,
compatibility, lifecycle-accounting, logs-only-scope, or test-adequacy
finding

All exact-head GitHub CI gates are green, including lint, unit tests,
PostgreSQL, relay/backend/desktop integration, both Linux server
cross-compiles, Windows/macOS builds, and security checks.

## Staging verification

- exact multi-architecture image:
`dev-sha-8faf7526822a119efa035e58b2b3c59aa67fc81d-run-33708188952-1`
- immutable manifest:
`sha256:26cad28266a6bb0b0e7081eb6091d374e5489f8bb78c475a4a65737dee86cc67`
- image workflow: https://github.com/block/buzz/actions/runs/33708188952
- focused staging deployment:
https://github.com/squareup/builderbot-platform-core-infrastructure/pull/314
- replacement ReplicaSet `buzz-d68764bc7` has two Ready pods with zero
restarts
- Datadog received one complete, contiguous sequence 1-12 from each pod;
both end with `process_telemetry/terminal/succeeded` at 3 ms
- queries scoped to the replacement ReplicaSet return no data for the
removed `buzz_startup_phase_terminal` or
`buzz_startup_phase_duration_seconds` families

The experimental Row 7 was removed from the Buzz Startup & Rollout
Safety dashboard. This logs-only PR deliberately adds no replacement
dashboard row.

---

**Update Sep 3, 12:26 ET:** Clarified the review boundary: this PR does
not close the broader #7238. Later exporter-task termination is
pre-existing runtime behavior and is now explicitly tracked in #7284; no
production code or staged image changed in this update.

Generated with Codex

Signed-off-by: Ravneet Arora <rarora@squareup.com>
2026-09-03 13:37:51 -04:00
Will Pfleger
752cbfc037 docs(nip-fi): add Blossom kind-24242 media possession-proof exception (#7278)
Adds the media/Blossom possession-proof exception section to
\`docs/nips/NIP-FI.md\`.

## What this changes

Encodes the condition set confirmed by Thufir's security review
(2026-09-03) as normative MUSTs in NIP-FI. Kind-24242 Blossom auth
events are admitted as the NIP-FI pairing possession proof for **media
routes only** — a named, bounded exception with an explicit precedent
fence against expansion.

### Scope fence

Kind-24242 proofs are valid only on:

| Proof type (`t` tag) | Valid route | Method |
|---|---|---|
| `upload` | `PUT /upload` (and temporary alias `PUT /media/upload`
until removed) | PUT |
| `get` | `GET /media/{hash…}`, `HEAD /media/{hash…}` | GET, HEAD |

All other protected routes MUST reject kind-24242 proofs.

### Upload proofs

Exactly one `x` tag over consumed body bytes; temporal check precedes
body consumption.

### Read proofs (the relaxation Will approved)

Host-wide MAY: no `x` required. Exactly one `server` tag matching the
resolved tenant host is a MUST. Optional `x` must match parent hash if
present.

Named residual (verbatim in spec): within at most 60 seconds from
minting (plus 5s future-skew), a captured full header set allows reading
any media blob on exactly one tenant host — read-only,
membership-checked, revocable, not state-changing, not cross-tenant.

### Freshness (Thufir option 2)

- `created_at <= now + 5s` (bounded future skew)
- `now - created_at <= 60s`
- Exactly one `expiration`, valid at admission, satisfying `expiration
<= created_at + 60s`

### Transport/cardinality

Exactly one each of `Authorization` (Nostr scheme), `t`, `expiration`,
`server`; `x` at most once; reject any duplicate, malformed, or
conflicting instance.

### Per-request pairing

Full assertion verification, exact key equality between assertion
`nostr_pubkey` and kind-24242 signer, deny-map enforcement on every
request. Stub gap named.

### Compliance note

PR #7264 implementation is explicitly non-compliant until the bounded
hardening task lands (named gaps: multi-tag acceptance, 3600s window,
optional `server`).

## Behavioral oracle

`FI-TRACE-HTTP-INGRESS` extended to cover kind-24242 admission and
denial cases.

## Scope

Docs-only. No code changes. The code hardening is a separate follow-on
task.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-09-03 12:48:23 -04:00
Logan Johnson
6cf514ed90 fix(desktop): wrap message tables within the available pane (#7279)
## Summary
- Let Markdown tables use the available message width instead of their
maximum-content width, inheriting the renderer's existing
`wrap-anywhere` handling for long tokens and links.
- Top-align header cells as well as body cells. Give cells a `min-w-24`
readability floor so short labels do not collapse into one-letter
columns; retain the existing table-local scrollbar when many columns
genuinely cannot fit.
- Preserve semantic table markup, links, inline code, and surrounding
message layout. Add three browser regressions through the real message
renderer (channel, narrow thread, and many-column overflow).

### Related issue
Fixes #5313. No matching open table-readability PR found.

Owner-authorized task channel: `819b36d5-7371-4ed0-bf9b-d461d723779e`

Source:
buzz://message?channel=819b36d5-7371-4ed0-bf9b-d461d723779e&id=3bf2d1bd5df28fcb481e71159dc1be2b3888918b05f67e270354fbfea65a8c90

### Testing
Original production candidate:
`4427f2e6f9ef85a01a267fe9eabd1cf13cacfb78`; base:
`7a9a5233d9d755e715be0c585cf7850e935d28cf`.

- `pnpm -C desktop check` — passed.
- `pnpm -C desktop test` — 6,110 passed, none skipped.
- `pnpm -C desktop typecheck` — passed.
- `BUZZ_PROTECTED_BUILD_OUTPUT=<isolated-production-directory> pnpm -C
desktop build` — passed.
- `pnpm -C desktop build:e2e --outDir <isolated-candidate-directory>` —
passed.
- `just file-size-check` and `git diff --check` — passed.
- Chromium / mock Tauri bridge: all 3 new Playwright tests passed
against the fixed candidate build. Both wrapping tests fail on the
unchanged base build, while the overflow-fallback test passes. Each
browser run used a unique output directory and a non-reused local server
pinned to its build directory.
- At 883px channel message width: table scroll width **1,395 → 883px**.
At 292px thread message width: **1,395 → 292px**. All cells top-aligned;
long tokens/URLs wrap, link destinations and code text remain intact,
short labels stay on one line, and document width remains 1,280px.
- Focused fresh-frame review checked actual before/after screenshots and
the complete diff. It caught one-letter label wrapping in an early
width-only candidate; the final cell-width floor and regression
assertion address that finding.

Limitations: native Tauri/WebKit and relay-backed integration were not
exercised for this CSS-only delta. Repository-wide `just ci` was
attempted but exceeded the local command budget during unrelated Rust
compilation; it is **not** reported green. The full affected TypeScript
package gates above passed on the exact candidate. Production/E2E builds
retain existing chunk-size and mixed static/dynamic import warnings.

Before/after screenshots are posted below using the repository's
screenshot publication script. No merge, production installation, or
runtime restart is requested.


### CI-driven test-only follow-up
Current head: `925d964b6cf31ba704d74baf73769110774b2789`.

Smoke shard 3 exposed an older test in `messaging.spec.ts:457` that
still required three columns of ordinary prose to overflow horizontally
([failure
log](https://github.com/block/buzz/actions/runs/33771325404/job/100702242706)).
This expectation contradicts the intended wrapping change. Updated its
name and assertion to require containment; the separate many-column
local-scroll test is unchanged. No production code or harness settings
changed in this follow-up.

- Affected browser validation: **4/4 passed** (updated existing
prose/narrow table case plus the three new channel/thread/overflow
cases).
- Reused the immutable `4427f2e6` E2E build on a fresh non-reused
server; production source is unchanged at the current head. The
package/build checks above remain evidence for that unchanged production
tree, not a claim of rerunning the full suite at the new SHA.
- Targeted Biome check, file-size gate, and `git diff --check` passed.
- Current-head CI:
https://github.com/block/buzz/actions/runs/33773490446
- Prior unrelated Bestie baseline failure and reproduction:
https://github.com/block/buzz/pull/7279#issuecomment-5528077181
- Security authorization and review must target the **current** head,
not the old screenshot/build SHA. A Block organization member must
comment exactly `@buzz-security-review
925d964b6cf31ba704d74baf73769110774b2789`.

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
2026-09-03 16:07:14 +00:00
Will Pfleger
7a9a5233d9 🤖 fix(desktop): harden smoke E2E tests against Bestie overlay and toast timing (#7270)
## What

Two remaining E2E hardening fixes from the Desktop Smoke flake pattern
introduced by `ac5a18697` (Bestie — added `VITE_BUZZ_BESTIE=1` to
`.env.e2e` and mounted `BestieGlobalOverlay` globally).

The toast/DM-retry fix landed independently in main via #7127
(`input.hover()` + bounded `toHaveCount` wait); that hunk is dropped
from this PR.

## Fixes

### `agent-control-regressions.spec.ts:240` — Stop does not accept an
unconfirmed or foreign-channel result

**Cause:** Playwright 1.60.0's `page.clock.install()` fakes all timers
including `requestAnimationFrame`. The test called it before opening the
settings menu. With RAF frozen, the `DropdownMenuContent`'s `zoom-in-95
duration-150` CSS enter-animation never advances — Playwright's
stability check observes a continuously-changing bounding box until the
30s test timeout.

**Fix:** Re-sequence so the menu is opened on real time first. After
`openAgentActivity`, open the trigger, assert visibility/enabled, call
`waitForAnimations(page)` to settle the enter-animation (real
`setTimeout`, no fake clock installed yet), then install the clock. The
`fastForward(8_001)` correlation timeout still works because it's
scheduled after the clock is active. Pointer actionability preserved —
normal `stop.click()` (no `force`) fails with pointer-interception under
a covering surface.

### `message-feedback-snapshots.spec.ts:97` — profile hover uses the
channel hover surface

**Cause:** `channel.hover()` triggers a CSS `transition-colors`
animation. With the Bestie `LayoutGroup` mounted, `evaluate()` captures
a mid-transition background value that never matches the profile card's
settled token.

**Fix:** `waitForAnimations(page)` after `channel.hover()` and before
reading `channelHoverColor`.

## Evidence

- Target specs pass: stop-turn 8/8, profile-hover passes.
- Full `agent-control-regressions.spec.ts` (7 tests) green.
- `just desktop-typecheck` clean at pushed head `0fbfe2a9f`.
- No production code changed.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
2026-09-03 08:59:49 -04:00