mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
0ef7a2222ef4e9b221f310ac60ca6a535d9c840d
2699 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4cd82f5132 |
feat(buzz-agent): add opt-in workspace-scoped Databricks reuse (#7606)
Posted by Brain, Wes Billman’s AI assistant, on behalf of `wesbillman`. ## Summary Add an opt-in `databricks::DatabricksConnection` over the existing PKCE/coordinator and v2 catalog machinery. It accepts an explicit HTTPS workspace origin, absolute caller-owned cache root, and browser opener; it does not start an agent or infer credentials/host from the environment. - Validate both OAuth endpoints on the actual discovery response used for grants; native HTTP redirects are disabled and requests have a 30-second timeout. - Keep strict cache/single-flight state in a separate namespace. Connect is explicit/user-initiated; catalog lookup and its one 401 refresh stay headless. Tokens are not exposed by the connection API. - Preserve existing legacy constructors, endpoint/redirect policy, cache layout, intents, and catalog filtering/pagination/partial/default semantics. No internal host default, credential migration, app integration or release configuration changes. ### Intentional shared changes OAuth success/discovery JSON is capped at 1 MiB; error JSON at 16 KiB. Oversized/malformed responses are infrastructure failures, while bounded client-error `invalid_grant` retains its existing classification. OAuth diagnostics omit raw bodies/URLs/opener/callback details. Overflowing token expiry returns an infrastructure failure. These changes affect legacy callers too and have synthetic regression coverage. See [the reuse contract](crates/buzz-agent/DATABRICKS_REUSE.md) for API, compatibility and caller obligations. Total operation deadlines, stale-result fencing, private root ownership, nonlogging opener behavior and credential-removal UX remain the caller’s responsibility. ### Related issue Related discovery work: #6918. Searched existing Databricks PRs and OAuth issues; no duplicate of this strict opt-in API was found among those results. This is a new reuse boundary, not a claim of a live credential leak. ### Testing Published head: `cf33eb2f4f267d5808e068e35127d5504f0bc674`, based on main `78618804ec86a014524ad7d1fb55928e8f5c3edf`. Two implementation/doc commits were transplanted from a release-derived local base to exclude unrelated release files. `git range-diff` reports both patches unchanged; independent source review of the transplant found no blocker. **At the published head:** normal pre-push hooks passed: branch skew, push-head scope, file-size checks, Rust test lane and desktop Tauri Clippy/tests. The fallback Rust lane actually ran `buzz-agent --lib`: 542 passed / 1 ignored, NOT the full package integration suite. Hosted full package/repository CI remains pending. The first push attempt was interrupted by the command runner’s 300-second limit; the second completed normally with all hooks enabled. **At reviewed implementation checkpoint `8cb3b72d4`:** 16 strict, 27 auth, 32 catalog and 63 coordinator/OAuth/CLI tests passed, independently rerun; 18 author-selected mutations and four independent reviewer mutations failed tests with passing restored controls. The subsequent `539319411` delta changed only two doc-comment blocks. These earlier test/mutation results are not silently attributed to the rebased head. Synthetic coverage includes HTTPS grants/catalog, off-origin endpoint and redirect rejection, cancellation/socket/listener cleanup, host/root/legacy namespace isolation, bounded declared/chunked bodies and exact-limit controls, redaction with positive log capture, malformed expiry, legacy endpoint/redirect controls, and real CLI cached-auth aliases. Desktop native checks used fail-closed sidecar stubs: compile/test evidence, **not** a runnable or packaged app. No real provider credentials, browser sign-in, app launch, or agent cutover was performed. Cross-platform execution and real-provider acceptance remain separate gates. No merge or downstream adoption is requested by this PR. --------- Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz> Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz> |
||
|
|
6c35e82bd5 |
fix(acp): improve hints in pi agent setup guide (#7594)
## Summary Handle missing Pi adapter setup and update its install guidance. ### Related issue None found. ### Testing No manual testing. Generated with Amp --------- Signed-off-by: Salman Mohammed <smohammed@squareup.com> Co-authored-by: Amp <amp@ampcode.com> |
||
|
|
f3940ff21e |
fix(ci): pull pinned MinIO images from Quay (#7599)
## Summary CI is failing in `main` before tests start because Docker Hub denies pulls for `minio/minio` and `minio/mc`. Retrying the failed jobs produced the same errors. Use MinIO’s Quay images in the development, harness, deployment Compose, and Helm configurations. Pin each image to its multi-platform digest and retain the release tags already used by deployment templates. Update the architecture reference to match. ### Related issue No duplicate issue or PR found. Addresses the image-pull failures in [main CI attempt 2](https://github.com/block/buzz/actions/runs/34645306855/attempts/2). ### Testing - Pulled both pinned images for Linux ARM64 and AMD64. - Started an isolated Compose stack derived from the updated MinIO service definitions, once per architecture (AMD64 under local emulation). - Verified the existing health check passed and the bucket initializer exited with code 0. - Uploaded an object with `mc cp`, read it with `mc cat`, compared its content, and deleted it with `mc rm` on both architectures. - Confirmed the initialized bucket was private and removed the temporary stack and volume. Generated with Codex Signed-off-by: Salman Mohammed <smohammed@squareup.com> |
||
|
|
78618804ec |
Configure ACP session scope per agent (#7578)
## Summary PR #6732 taught the ACP harness to honor `BUZZ_ACP_SESSION_POLICY`, but the desktop exposed that policy as one global experiment. Turning it on changed every managed agent at once, even though conversation scope is part of how an individual agent should behave. Some agents need continuity across a channel, while agents such as validators may need clean context for each thread. This moves the choice onto the agent definition under Advanced as **Conversation context**, with **Entire channel** and **Each thread** options. **Entire channel** remains the default, so existing definitions keep their current behavior. The policy now travels through create and update IPC, returned agent summaries, persona events, snapshots, teams, and community catalog copies. Launches pass the effective definition value to the harness. Editing a running agent uses the same lifecycle as a model edit: it shows the restart-required state, leaves the deployed process untouched, and applies the new policy on restart. The global Thread Scoped ACP Sessions experiment and its desktop command/state have been removed because the setting no longer has a useful global meaning. There is intentionally no migration for the old preview override. Users who enabled it can choose **Each thread** for the agents that need it. Unknown or null policy values degrade to **Entire channel** across stored agents, persona events, and catalog projections. This keeps forward-version data from dropping complete agent records. ### Related issue No matching issue found. Builds on #6732. ### Testing - Focused Playwright create flow selected **Each thread** and verified `create_persona` received `sessionPolicy: "thread"`. - Focused Playwright catalog flow added a thread-scoped agent and verified the local copy stored `session_policy: "thread"`. Generated with Codex --------- Signed-off-by: Salman Mohammed <smohammed@squareup.com> |
||
|
|
e17cdd9d5c |
refactor(buzz-acp): point agents at buzz --help instead of a command table (#7586)
## Summary The ACP base prompt carried a `Group | Key commands` table for the `buzz` CLI — a second copy of a surface that already documents itself, with nothing tying it to the parser. It had already drifted: 16 of the CLI's 23 groups (missing `emoji`, `gifs`, `notes`, `patches`, `media`, `moderation`, `pack`) and 50 of the 91 subcommands under the groups it did list. The adjacent exit-code line stopped at 4; the CLI has a 5th (5 = write conflict, NIP-33 LWW). #7584 was the prerequisite: `buzz --help` now renders the full command tree — every group, its subcommands, and their descriptions — in one invocation (199 lines, 13 KB), generated from clap's own command definition, so it cannot drift. `buzz -h` still prints the group-level summary. Pointing the prompt at `--help` was not viable before that, because discovering `buzz messages send` cost a second call and discovering `buzz canvas set` exists cost a third. So this replaces the table with a pointer to `buzz --help`, fixes the exit codes, and adds the one CLI ergonomic that `--help` does not make obvious (`--format compact` is a global flag that goes before the subcommand). Everything after the table is kept verbatim — it is the non-discoverable semantics `--help` cannot express: multiline content through stdin, the `BUZZ_AUTH_TAG` requirement for agent drafts, `--channel` on `pr open`, the `link` field, mention and assignment rules. Signed-off-by: Salman Mohammed <smohammed@squareup.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
44c1cc7df8 |
feat(buzz-cli): render an agent-friendly command tree in --help (#7584)
## Summary
`crates/buzz-acp/src/base_prompt.md` carries a hand-maintained `Group |
Key commands` table of the `buzz` CLI. It is a second copy of a surface
that already documents itself, and it has drifted: it lists 16 of the
CLI's 23 groups (missing `emoji`, `gifs`, `notes`, `patches`, `media`,
`moderation`, `pack`) and omits roughly two-thirds of the subcommands
under the groups it does list. its hard to keep it in sync.
The reason the base prompt duplicates that inventory is that `buzz
--help` was not good enough to lean on. It listed 23 group names with
nothing under them, so finding `buzz messages send` cost a second
`--help` call and learning that `buzz canvas set` exists at all cost a
third. An agent paying per group reasonably prefers a stale table it
already has.
This PR fixes the help output so the prompt can point at it instead.
`buzz --help` now prints the whole tree — every group, its subcommands,
and their descriptions — in one invocation:
**Before**
```
Commands:
messages Send, read, search, and manage messages
channels Create, configure, and manage channels
canvas Get and set channel canvas documents
```
**After**
```
Commands:
messages Send, read, search, and manage messages
send Send a message to a channel
send-diff Send a code diff / patch to a channel
edit Edit a previously sent message
...
repos Announce and discover git repositories (NIP-34)
protect Manage branch and tag protection rules on one of your repositories
list List the repository's protection rules
```
This change is formatting only: it adds no new data and no new commands.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Signed-off-by: Salman Mohammed <smohammed@squareup.com>
|
||
|
|
ec11f8e2f3 |
fix(avatars): scale agent squircles from normalized paths (#7307)
## Overview **Category:** fix **User Impact:** Agent avatars and empty agent-team placeholders now keep the same intentional squircle silhouette at every size on desktop, while human avatars remain circular. **Problem:** Desktop agent avatars used percentage-rounded rectangles, which made the silhouette vary by surface and required each avatar implementation to recreate the shape. Empty team placeholders also drew a regular CSS border inside the clip, leaving straight inset edges that looked like a cropped square. **Solution:** Reuse one normalized SVG clip path across desktop artwork. Empty team placeholders use a separately clipped squircle outline behind the clipped surface, so both the fill and outline follow the canonical silhouette. > **Scope correction:** Mobile is deferred from this PR because a real Flutter capture could not be obtained reliably in the current build environment. Previously attached mobile images were hand-composed synthetic layouts, not captures from the running app; they have been removed and must not be treated as validation. The mobile implementation is being reverted in one follow-up commit so it can be reapplied and verified separately. ### Related issue Follow-up to #7106. ## Changes <details> <summary>File changes</summary> **desktop/src/features/agents/ui/AgentCreationPreview.tsx** Applies the shared squircle clip to agent creation preview images and fallbacks. **desktop/src/features/agents/ui/AgentSessionToolItem/CompactMessageSummary.tsx** Uses the shared agent-avatar silhouette in compact session summaries. **desktop/src/features/agents/ui/IdentityInitialsAvatar.tsx** Clips agent initials with the normalized squircle instead of a percentage radius. **desktop/src/features/agents/ui/TeamIdentityCard.tsx** Uses the shared squircle for agent identities and gives empty teams a separately clipped squircle outline instead of a regular inset border. **desktop/src/features/agents/ui/activityRenderClasses/UserMessageBubble.tsx** Keeps agent avatars in activity message bubbles on the canonical silhouette. **desktop/src/features/home/ui/InboxListPane.tsx** Applies the shared clip to agent avatars in the inbox list. **desktop/src/features/home/ui/InboxMessageRow.tsx** Applies the shared clip to agent avatars in inbox message rows. **desktop/src/features/huddle/components/ParticipantList.tsx** Uses the normalized squircle for agent participants while preserving circular human avatars. **desktop/src/features/messages/ui/MessageRow.tsx** Clips agent message avatars through the shared definition. **desktop/src/features/messages/ui/SystemMessageAvatars.tsx** Uses the canonical agent shape in system-message avatar groups. **desktop/src/features/messages/ui/TypingIndicatorRow.tsx** Uses the canonical agent shape in typing indicators. **desktop/src/features/profile/ui/ProfileAvatar.tsx** Scales the same squircle to the larger agent profile avatar. **desktop/src/features/profile/ui/SelectedRecipientChip.tsx** Uses the shared shape for selected agent recipients. **desktop/src/features/projects/ui/IssueAssigneesRow.tsx** Applies the agent squircle to issue assignees without changing human avatar geometry. **desktop/src/features/projects/ui/ProjectCards.tsx** Uses the canonical shape for agent avatars on project cards. **desktop/src/features/projects/ui/ProjectEntityListRow.tsx** Uses the canonical shape for agent identities in project entity lists. **desktop/src/features/projects/ui/ProjectsActivityFeed.tsx** Uses the shared clip for agent avatars in project activity. **desktop/src/features/projects/ui/ProjectsOverviewRail.tsx** Uses the shared clip for agent avatars in the project overview rail. **desktop/src/main.tsx** Mounts the single shared SVG clip-path definition once for the desktop application. **desktop/src/shared/lib/cn.ts** Registers `rounded-squircle` in the border-radius class group so shadcn-style `cn(...)` composition resolves it against `rounded-full` by normal last-class precedence. **desktop/src/shared/styles/globals/utilities.css** Registers the reusable Tailwind `rounded-squircle` utility and references the normalized SVG clip path without agent-specific CSS selectors. **desktop/src/shared/ui/AvatarClipPaths.tsx** Owns the reusable normalized object-bounding-box `rounded-squircle` path so all desktop sizes reuse one definition. **desktop/src/shared/ui/UserAvatar.tsx** Routes the existing squircle shape option through the shared SVG clip. **desktop/tests/e2e/agents.spec.ts** Verifies team facepile and empty-team placeholder surfaces/outlines use the canonical clip without a rectangular border. **desktop/tests/e2e/messaging.spec.ts** Verifies agent message and profile avatars reuse one clip definition and scale across rendered sizes. </details> ## Reproduction steps 1. Run the desktop app and open a channel containing both agent and human messages. 2. Compare agent avatars in the message list, inbox, huddle participants, projects, and profile panel. Agent images and fallbacks should share one smooth squircle silhouette at each size; human avatars should remain circular. 3. Open **Agents** and inspect an empty agent-team card. Its placeholder fill and outline should both be smooth squircles, with no straight inset cropped-square edges. 4. Open an agent profile from a message and confirm the larger profile avatar keeps the same proportions as the smaller message avatar. ## Testing - Desktop Biome check passed for the changed empty-team component and Agents E2E spec. - Desktop typecheck and differential file-size check passed. - Full desktop unit suite passed: 6,453 tests. - Full Agents integration spec passed: 37/37, including the empty-team outline regression. - Post-rebase pre-push gates passed at `304b159a2`: desktop checks, TypeScript typecheck, differential file-size gate, and all 6,453 desktop unit tests. - The shadcn-aligned `rounded-squircle` registration passed changed-file Biome, desktop TypeScript/build, and pre-push desktop checks at `27e0c12d3`; the full desktop unit suite passed 6,459/6,459. ## Screenshots Screenshots are temporarily omitted. The previous captures used the desktop E2E mock bridge and were not evidence from a live running app, so they were removed. --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> |
||
|
|
d074576871 |
fix(mobile): bind same-name mentions to exact selected identities (#7385)
<!-- Draft PR body for #7385 · fix(mobile): bind same-name mentions to exact selected identities · https://github.com/block/buzz/pull/7385 --> 🤖 ## Summary If two people in a channel share a display name, mobile couldn't tell them apart in mentions: picking the second could overwrite the first's selection, a rendered mention linked whichever same-name person matched first, and a later rename or a shorter name could re-bind the text to the wrong recipient. This PR binds every mention to the exact selected identity: - Each same-name selection keeps its own recipient instead of overwriting by name; conflicting picks get a qualified label like `Name (key…)`. - The longest matching label wins, so a shorter or interior name can never claim part of a longer one and steal its identity. - Rendering resolves recipients by the signed identity key from the event's tags — never from message text alone — so qualified labels stay correct regardless of tag order and survive later renames; an untagged ambiguous label blocks shorter mentions instead of silently re-binding. Ports the landed Desktop exact-recipient behavior (see `docs/mention-editor.md`). ### Related issue - Fixes: N/A. No mobile issue; Desktop's landed exact-recipient fixes are the reference this ports. - Independent base (`main`). #7387 (child) persists these exact selections in saved drafts. - Landing note: branches in this series overlap in the composer — when rebasing, keep exact/durable mention bindings, the explicit invite/reference-only choice, the account/visit/revision fences, and authorization before membership preparation and publication; don't resolve conflicts by taking either side wholesale. - Draft — not requesting merge yet; the security advisory run for this range timed out without results (no verdict). ### Testing - Regressions cover same-name collisions, prefix/overlap, removal, tag order, and renames. - At `acd841354a692243f1cb4c04059baad42f1d2abb`: `just mobile-check` and full `just mobile-test` pass (2,082 tests). The earlier full `just ci` receipt linked below is reused only for unchanged non-mobile code/tooling, not claimed as a rerun at this head. - Previous-head evidence: `just mobile-check`, the full mobile test suite, and full local `just ci` all pass — receipts in the [exact-head evidence comment](https://github.com/block/buzz/pull/7385#issuecomment-5574635836). - Verification is widget-test level; no native device or simulator run is claimed. To see it: mention two teammates with the same display name — both stay distinct, the second shows a qualified label, and the rendered message keeps both correct even after either renames. ### Screenshots Flutter production-widget test renders — not native-device screenshots or acceptance captures. | Scenario | Before | After | |---|---|---| | Qualified mention chip for same-name recipients, at 200% text scale (deliberate stress fixture) |  |  | <details> <summary>Capture provenance</summary> Rendered by the Flutter widget engine in a `flutter test` run (production widgets, production theme; no device or simulator). Before: this PR's declared base `3c7f288c60d67df78577b237e27c3dfc8831aaa1`. After: its head `39afd73b0adfde14164f4b10dbd089cb498312b6`. </details> Signed-off-by: Logan Johnson <loganj@squareup.com> |
||
|
|
f3408fc620 |
fix(desktop): isolate quota backoff and reuse channel discovery rosters (#6998)
Pinky, an AI agent, is updating this PR on Wes's behalf. ## Targeted repair, not a startup rewrite This replaces the previous readiness/replay candidate. Current head: **`83ac81ff6ae439b0e68f4a3dafd4b2af6dbadcdb`**, two independent commits on main base `3c7f288c60d67df78577b237e27c3dfc8831aaa1`: 1. **`0b6681de2` — keep quota backoff on its transport.** HTTP 429s retain native HTTP backoff without arming the renderer WebSocket gate. WebSocket quota/concurrency CLOSED retains its retry deadline without damping HTTP's separate quota. The explicit `rate-limited: shared admission unavailable` signal still damps HTTP too. Reuse existing all-channel live streams for Home mention invalidation instead of a second per-channel mention subscription; retain kind, recipient, self, membership, replay-deduplication, notification and unread boundaries. 2. **`83ac81ff6` — reuse complete membership rosters within each channel fetch.** Discovery already returns full paginated rosters. Retain those results and request only uncovered directory/pending-owner channel IDs. Skip empty fallback filters and preserve covered counts/pubkeys if that tolerant fallback fails. The next fetch reads current membership again; there is no persistent cache or new invalidation state. No send-route, AUTH-readiness, replay-ordering, scheduler, pacing, relay policy, dependency, or authorization changes. Refresh triggers and activity batching stay unchanged. Independent tracing did not establish a safe-to-delete duplicate normal-startup refresh; recovery invalidations at different replay stages are not interchangeable. **Size:** 18 files, +1,066/-350 overall; nine production paths +76/-205 (**129 fewer production lines**). The roster follow-up itself touches only `fetch.rs` (+31/-14) and its new 378-line regression file. Test bulk is not new startup machinery. ## Comparative roster measurement Actual native HTTP fetch code against a synthetic Axum loopback fixture: 265 channels × 100 members, deterministic signed events, debug build. Seven iterations per condition; medians below exclude the first warm-up. Baseline is `0b6681de2` production fetch, candidate is `83ac81ff6` production fetch, with the same temporary benchmark harness. | Measure | Baseline | Candidate | | --- | ---: | ---: | | HTTP reads per fetch | 7 | 6 | | JSON response-body bytes per fetch | 4,803,136 | 2,453,380 | | Median, no artificial request delay | 262.9 ms | 149.3 ms | | Median, 25 ms artificial delay per request | 376.7 ms | 282.7 ms | Channel/member assertions and output hash (`fa7cc551bc855e4d`) match throughout. This removes **one batched roster request**, not 265 requests. The measured ~43%/~25% reductions describe this large-roster synthetic fixture, **not production startup latency**. Activity requests run concurrently with roster enrichment, so real savings vary. Counts now use the discovery snapshot rather than a later reread within that same non-atomic fetch. ## Verification At pushed head **`83ac81ff6`**: - All enabled pre-push gates passed without bypass: `push-head-scope`, `branch-skew`, `desktop-check`, `file-size-check`, `desktop-typecheck`, `desktop-test`, `desktop-tauri-checks` (native fmt/Clippy/tests). - Full Desktop frontend suite: **6,457 passed**, zero failures; separate tooling suite: **10 passed**. - Full native Desktop workspace: **3,276 passed**, zero failures, 20 ignored, across unit/integration/doc bins. The main native lib accounts for 3,175 passed / 19 ignored; this is not 3,276 lib tests. - `pnpm build` passed the protected-feature artifact matrix; `pnpm build:e2e` also passed. Existing chunk-size/dynamic-import warnings remain. No full repository `just ci` run locally. - **130 selected headless mock-bridge smoke cases passed across two runs**: messaging, reconnect, reconnect affordances, inbox live updates, sidebar snapshots, send-channel binding, boot splash. The first invocation hit the shell's 300-second limit after 117 passes and no failures; all remaining 13 passed in a separate invocation. This is not a claim the interrupted invocation completed. - Six new tests invoke production `fetch_channels` through the real native HTTP client and cover 265/501-channel pagination (including same-timestamp continuation), exact requests, complete/deduplicated rosters, directory/pending fallback, hidden DMs, repeated membership/identity/relay changes, empty lists and errors. - Falsification in an isolated source copy: restoring the redundant baseline fetch fails four roster regressions; removing missing-roster fallback fails two. Restored candidate passes all six. Earlier transport mutations fail HTTP→WS coupling, WS→HTTP coupling, omitted shared-outage damping and duplicate-mention regressions. No mutation was applied to the committed worktree. - Brain's independent static reviews found no blockers in the transport repair or immutable roster patch. Source tracing confirms relay discovery serializes complete rosters, not projected matching p-tags. This was source review, not independent test execution or approval. ### Hosted CI [CI run 34185670504](https://github.com/block/buzz/actions/runs/34185670504) **completed successfully** for this head. GitHub checked out merge ref `25bcf09627e129887481d4441a1af3c6cb0af1ff`; its Git tree is identical to `83ac81ff6` (`49a01f3c2ebf4e547bbe3cd31406f984ac6cefe8`), verified through the GitHub Git API. The final PR rollup has 33 successful checks and 28 skipped, with none failing or pending. - Desktop Core passed frontend/native suites and compiled-feature verification. Windows Rust tests, Windows Desktop build, macOS `.app`/`.dmg` build, all four smoke shards and both relay-backed E2E job shards passed, as did Rust lint, DCO, Semgrep and zizmor. - Browser job totals: **1,620 passed, 13 flaky cases passing automatic retry #1, five skipped, zero terminal failures**. Smoke accounts for 1,389 passed / 12 flaky / three skipped; integration jobs report 231 passed / one flaky / two skipped. No manual CI reruns or source changes to obtain green. - The retries cover channel-browser, empty-edit, huddle menu, runtime-install, overscroll, onboarding avatar, video attachment and workflow UI assertions. They are disclosed, not collectively declared unrelated. The video case reaches the changed real TS all-channel hook despite mocking native I/O: independent tracing found no regression mechanism or blocker, but the missing second-video player remains **unexplained**, and an indirect timing effect is not excluded. The self-authored, unmentioned event cannot use either the new mention branch or the removed old mention handler as a delivery path. A passing retry does not diagnose its cause. - The separate “Desktop Release Candidate” check skipped its validation step; it is not additional release validation. Codex security review was skipped, not passed. ## Remaining acceptance limits Wes's earlier manual trial of the transport-only candidate reported improvement; relay readback found both a top-level message and a thread reply. That was not a fingerprinted or timed trial of the new roster commit. The remaining few-second top-level send wait is **unattributed**, and this PR does not claim to resolve every startup delay. No visible app launch, relaunch, laptop input automation, or production test messages during this follow-up. Local native tests use fixtures; browser suites do not run the Tauri backend. CI building an app does not validate bundled macOS alerts or inactive-channel notification delivery. Those bundled real-workflow checks remain outstanding. No visual UI changes or screenshots; no approval or merge performed. Originating conversation: buzz://message?channel=5b130804-d759-40ad-a564-d64cc907fa8e&id=cb432417e290e06a1842639cfe0268f9d43d186defbcb59260930d129a26af83 --------- Signed-off-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz> Co-authored-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz> |
||
|
|
9847b0967d |
test(desktop): isolate login-shell probe measurements (#7570)
Pinky, acting on Wes’s behalf. ## Summary Fix a pre-existing false-failure mechanism in the login-shell discovery tests, independently of the thread-window work. - Run the two process-global-counter assertions in separate, bounded libtest processes. The global counter still sees all of each test’s auth/worker threads; no production discovery or cache behavior changes. - Require exact test selection, successful child exit, and a completion receipt after the assertion body. Reject counter resets outside isolation. - Keep the cold/repeated cheap-discovery zero assertions and forced-discovery positive control unchanged. Add parent-probe exclusion and own-worker controls. Three test-only files, +103/-4. Base `00209076c7a10d9e4a475466c313e8ebecf041f5`; head `07f8e86c58a0aa5378537a3daa4610361fdbad55`. ## Reproduction and causal checks On the main-based source, cheap discovery alone counted 0. Running the existing missing-binary version test on another thread inside the measurement interval made the repeated-cheap assertion fail with 1. This reproduces interference, not the exact scheduling/caller behind the original full-suite failure. The counter measures `run_in_login_shell` invocations, not exact OS process counts. Mutation checks: - Removing isolation fails the measurement guard. - Removing the real production `record()` call fails the original forced positive control (0). - A reviewer-identified early-return mutation initially survived; the final helper owns the assertion closure instead. Deleting that invocation now fails the external worker assertion (0 vs 1). Independent read-only review by Brain found no remaining blocking finding at the final head. ## Validation at the final head - **Full `just ci`: passed.** Desktop Tauri: 3,175 passed, 19 ignored; desktop JS: 6,488 passed; mobile: 2,098 passed. Remaining Rust unit, formatting/lint/static, and desktop/web build lanes passed. An initial five-minute tool timeout interrupted compilation; the subsequent complete run used a longer deadline, not altered gates. - **Normal pre-commit and pre-push hooks: passed.** Push includes default + mesh Tauri Clippy, complete Tauri workspace tests, size and branch-scope checks. No bypass. - **Real local relay, following `TESTING.md`: passed.** Built release `buzz-relay`, `buzz`, and `buzz-admin` from this head. Fresh isolated Postgres/Redis, additive migrations, dedicated loopback ports, disposable NIP-98 identity; health, create channel, signed root/reply send, exact-ID get, and thread retrieval passed. Relay and both owned containers stopped afterward. Dev auth-token mode follows the guide; not a production-auth or thread-window/native UI acceptance claim. ## Scope and limits This is process/cache isolation, not a filesystem sandbox. The existing bounded-command helper bounds child runtime/output; its Unix process-group behavior does not guarantee ownership of nested probe groups. Existing individual probe deadlines remain unchanged. No UI change, screenshots, thread-window commits, feature enablement, merge, or deployment included. Originating conversation: buzz://message?channel=0b73400d-6922-4fe3-9b97-454da04d011b&id=984528c883faa8829971d19394870f0f2d543d5c1950565fd55ffddc1b8d8022 Signed-off-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz> Co-authored-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz> |
||
|
|
6146c4fd16 |
feat(git): add default-branch management to relay and CLI (#7562)
Authored by Brain and opened on behalf of Wes (`wesbillman`).
## Summary
Add `buzz repos default-branch get/set` and the relay operation it
needs. Git push changes branch tips but cannot select the server's
symbolic HEAD. This selects an existing branch without deleting
branches, moving refs, or changing packs.
- Reuse immutable Git manifests and captured-ETag CAS; concurrent
pushes/default changes conflict, including stale no-ops.
- Require request-specific NIP-98, body binding, fail-closed replay
protection, host tenancy, current channel membership and
repository-management authority. Push/project roles alone are
insufficient.
- Send the CLI mutation once, without redirects; ambiguous delivery
retains the observed digest and is non-retryable.
- Document the narrow HTTP exception to Nostr-first, delegation scope
and admission-time ACL revocation. Kind:30618 remains derived; the
manifest CAS is authoritative.
- Register the Postgres/Redis/MinIO route and fresh-clone tests in the
existing Backend Integration lane.
## Review fixes — `7457bda50afa6b52e36315572fa96928b2e21db6`
- Reject signed valueless `payload` tags when verifying a body; preserve
the shared verifier's intentional absent-payload compatibility. The real
settings route now proves 401, unchanged manifest pointer, and unchanged
kind:30618 event IDs for malformed and other invalid credentials.
- Require a nonempty returned branch and `head == refs/heads/{branch}`;
SET additionally requires the returned branch to equal the attempted
branch. Malformed/mismatched mutation responses remain non-retryable
`DeliveryUnknown`, retaining the attempted branch and original digest.
Invalid reads fail before any POST.
- Strengthen the later-push/fresh-clone regression to select
`release/v1` while `main` exists.
## Verification
At `7457bda50afa6b52e36315572fa96928b2e21db6`:
- Full pre-push passed (all 14 Rust test groups, serialized Tauri
tests/clippy, file-size and branch-skew gates; 522 seconds). No hooks
disabled.
- All four isolated Postgres/Redis/MinIO settings tests pass, including
real Smart HTTP `ls-remote --symref` and fresh clone on `release/v1`.
- All 15 focused NIP-98 verifier tests and three default-branch CLI
tests pass; malformed credentials and missing-branch responses were
reproduced failing before the fixes.
- Five targeted mutants are killed: restoring the valueless-payload
bypass, weakening canonical HEAD validation, accepting an empty branch,
dropping requested-branch confirmation, and omitting hydrated HEAD for
the selected non-main branch. Mutations ran in a separate worktree and
target directory; original patch bytes were verified restored.
- `cargo clippy -p buzz-auth -p buzz-cli -p buzz-relay --all-targets --
-D warnings`, formatting, diff and file-size checks pass on the fixed
tree.
Earlier validation at `bb195b140`: seven Git authorization database
regressions and 28 existing transport tests passed (three infrastructure
cases in that module not selected); four mutations killed
management/replay/CAS/redirect regressions. Full pre-push passed with
`RUST_TEST_THREADS=1` (all 14 Rust groups, Tauri clippy and 3,173 tests,
size/branch-skew gates). Serial execution avoids an unrelated
process-global Desktop discovery counter flake; no hooks disabled or
Desktop changes.
Carl and Jude's review findings are addressed in the new head; **fresh
reviewer and exact-head security confirmation remain required**. The
earlier source review is not approval of this revision. No claim of
green new-head CI.
## Rollout
Merge and **deploy the relay support first**, then build/use the updated
CLI and explicitly set/verify the desired default. No schema migration.
This PR does not deploy anything or change a live repository.
Originating conversation (channel
`8e2fda80-9142-4df8-8e14-da288bea6471`):
buzz://message?channel=8e2fda80-9142-4df8-8e14-da288bea6471&id=8a7a9cf12737abc1e16605f241300be940a1691ef0992b35197afe26c563108d
---------
Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
|
||
|
|
813bbd1412 |
fix(acp): integrate the Buzz Pi adapter fork (#7552)
## Summary PR #7335 worked around missing Pi adapter support by generating a private Pi launcher and injecting Buzz's standing prompt and skills at process launch. The Buzz Pi fork now carries the required adapter extensions, so this removes that launcher and returns prompt construction to the normal ACP session path while retaining the base-prompt composition introduced by #7335. The Pi preset now installs `salman1993/pi-acp` and launches its renamed `buzz-pi-acp` binary. Buzz adds `-- --skill <harness-cwd>/.agents/skills` only when launching that binary, sends the complete composed prompt as the `_meta.systemPrompt` replacement string on `session/new` only when `initialize.agentInfo.name` is `buzz-pi-acp`, and sends the scoped title alongside it as `_meta.sessionTitle`. The fork identity is treated as system-prompt capable regardless of its reported ACP protocol version, which prevents duplicate legacy user-message framing. Upstream `pi-acp` does not receive either fork-specific behavior. Observer transcript projection accepts the string, `{ replace }`, and `{ append }` metadata forms. The fork now stores restore metadata in one atomic file per session under `~/.pi/buzz-pi-acp/sessions/`. This prevents concurrent Buzz workers from overwriting another session's prompt or title. The fix landed in [salman1993/pi-acp#9](https://github.com/salman1993/pi-acp/pull/9). This supersedes the closed #7508. No agent-configuration rules changed; this changes the Buzz Pi adapter contract and launch arguments. ### Related issue #7329 ### Testing Installed fork commit `09cf07e436b8f18e52401558f988f31a15702313` through the documented Git URL. The installed bundle matched the committed `dist/index.js` byte for byte and contained the `~/.pi/buzz-pi-acp` metadata path. The fork's 106 non-skipped tests, typecheck, and lint pass. Ran the ignored real-Pi integration test through Buzz's production session composer. The test exercised the renamed package, `agentInfo.name`, and the new per-session metadata store. Base, persona, team, core-memory, huddle, canvas, and skill markers each appeared once after switching sessions and again after restarting the adapter, while the other session and Pi's native default prompt were absent. Added regression coverage proving `buzz-pi-acp` receives fork-specific system-prompt metadata and managed skills while upstream `pi-acp` does not. `just ci` passes. Generated with Codex --------- Signed-off-by: Salman Mohammed <smohammed@squareup.com> |
||
|
|
092c6a7277 |
fix(markdown): align mention chip wrapping (#7501)
**Category:** fix **User Impact:** Human and agent mentions now break across lines with the same cloned chip treatment as repository and permalink chips while preserving the conversation text rhythm. **Problem:** Profile-backed rendered mentions sat inside an `inline-flex` popover trigger, unlike entity chips, so the wrapper interfered with true inline fragmentation. The browser-layout test measured text-range rows rather than the painted chip rectangles, allowing touching decorations to pass as “separate” fragments. **Solution:** Keep the profile trigger interactive but override its layout to true `inline`, then give mention fragments 18px computed leading inside the message’s 20px prose rhythm. Chromium paints each fragment at 17px and advances it by 20px, leaving a visible gap between cloned rounded rectangles. The browser test now measures the chip’s own `getClientRects()` and asserts fragment count, height, gap, and step; entity links retain their existing 22px leading. <details> <summary>File changes</summary> **desktop/src/features/profile/ui/UserProfilePopover.tsx** Allows inline consumers to override the trigger wrapper’s layout without changing other profile-popover call sites. **desktop/src/shared/styles/globals/markdown.css** Keeps one shared wrapping-chip mechanic and gives mention decorations enough room to separate visibly within 20px prose. **desktop/src/shared/ui/markdown.test.mjs** Pins both rendered mentions and entity links to the shared wrapping-chip contract. **desktop/src/shared/ui/markdown/MarkdownMention.tsx** Makes the profile-popover trigger truly inline so the nested mention chip can fragment with surrounding prose. **desktop/src/shared/ui/mentionChip.ts** Keeps `wrapping-inline-chip` as the single contract for fragmenting decorated chips. **desktop/tests/e2e/mentions.spec.ts** Measures the painted chip rectangles, requires a positive fragment gap, and verifies the inline trigger remains mouse- and keyboard-operable. **desktop/tests/e2e/navigation.spec.ts** Keeps a wrapped repository chip as the control, asserting its existing 22px line height and fragment advance. </details> ## Reproduction steps 1. Open a channel in Buzz Desktop using dark theme. 2. Send a message containing a human mention and another containing an agent mention; both chips should remain aligned with adjacent text on a 20px line. 3. Render a collision-qualified mention in a narrow message width; it should break into separately decorated fragments exactly like another wrapping chip, while each fragment follows the 20px prose rhythm. 4. Render a long repository or permalink chip in the same constrained width; it should retain its roomier 22px fragment spacing. ## Screenshot The dark-theme production renderer shows the real qualified label (`bob (npub1hv3…tpuc)`) at an 8rem width. The two lines now paint as visibly separate rounded fragments rather than one continuous rectangle.  ## Validation At commit `2b063e1b4ade30e11f1616269ad4ba4190366885`: - Pre-push desktop gates — file-size check, Biome/checks, typecheck, and 6,483 unit tests passed - `pnpm --dir desktop build` — passed - Focused Playwright coverage for single-line agent mention, single-line human mention, wrapped qualified mention including keyboard profile activation, and timeline mention click — 4 passed - `git diff --check` — passed --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Rizz <rizz@agents.buzz> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> |
||
|
|
00209076c7 |
fix(relay): reject presence updates when Redis storage fails (#7532)
## Summary
- Reject kind:20001 presence events with `OK false` / `error: presence
storage unavailable` when Redis SET or DEL fails, before publishing,
local fan-out, or local-event marking.
- Preserve the producer contract needed by snapshot-confirming
consumers: delivered live presence must follow successful mutation of
the Redis state read by snapshots.
- Classify those backend rejections with the existing `IngestError`
taxonomy so a presence storage outage counts as
`buzz_events_rejected_total{transport="ws",reason="error"}`, not client
`reason="invalid"`; genuine client-input refusals (verification failure,
membership gates) stay `invalid`, and every wire message is an unchanged
fixed sanitized string (review follow-up, no protocol wording change).
- Add actual `handle_event` integration coverage for rejected
online/offline transitions, healthy online→offline
accepted/stored/fanned-out behavior, and the rejection-counter routing
on storage failure with an invalid-signature control.
This is standalone on main; it does not depend on the mobile
implementation. Deploy this relay prerequisite before relying on #7526's
snapshot-confirmation policy. Existing
pubsub-failure-after-successful-storage behavior and disconnect TTL
cleanup are deliberately unchanged. A storage error may be an ambiguous
write outcome, not a rollback guarantee; the rejected event is not
published by this handler. Clients may retry the generic `error:`
rejection. Desktop's 60s heartbeat retries non-offline presence, not
every explicit offline transition.
### Related issue
Addresses the relay prerequisite identified in [#7526 review
5157607827](https://github.com/block/buzz/pull/7526#pullrequestreview-5157607827).
Searched open presence/storage PRs; no duplicate relay storage-error
rejection fix found. #7382/#7383/#7526 heads and bases are unchanged.
### Testing
Exact head: `389174df29cc02d0f885c03209eff661d8bb2ec0` (+380/-13; 393
total), one commit `389174df2` on top of the reviewed `c031d6eb1`
(DCO-signed; base `bfc384855889432df4a333a0edf3080f332ee169` unchanged).
- PASS: `cargo fmt --all -- --check`, `cargo clippy -p buzz-relay
--all-targets -- -D warnings`, `git diff --check`, `just
file-size-check`, PostgreSQL discovery validation — all run at the exact
final head with a clean tree before and after.
- PASS: documented native `scripts/postgres-test-run.sh -p buzz-relay
--lib --tests`: **89/89** actual integration tests, including the four
presence cases (online/offline storage rejection, healthy
online→offline, and the new rejection-classification case). Owned
PostgreSQL 17/Redis on isolated loopback ports, schema plus
reconciliation applied; no shared development database.
- PASS: explicit `cargo test -p buzz-relay presence_storage -- --ignored
--nocapture`: **4/4**, not skipped.
- Full isolated relay crate suite at the final head (`cargo nextest run
-p buzz-relay --lib --tests`): **1062 run: 1062 passed, 94 skipped**.
The previously failing
`api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo` passed
in this run (1.5s); it is a known timing-sensitive main baseline failure
tracked open in #7140 and untouched by this PR, so this single passing
run is reported as-is and does not claim environmental clearance or
close #7140. No full-suite-green claim is made beyond this run.
- Mobile is untouched; #7526's existing 2090-test/format/analyze
evidence remains scoped to its unchanged head. Its separate Desktop
Smoke E2E (2) failure remains red; no CI retries requested.
[Production-seam regression
coverage](
|
||
|
|
cec5c8fd92 |
fix(desktop): let inbox title and message author names truncate under narrow panes (#7550)
## Summary Fixes two instances of the same dead-truncate pattern in the desktop app, where a flex item's implicit `min-width: auto` prevented `truncate` from engaging, so long text painted over adjacent controls instead of ellipsizing: - **Inbox detail title** (`InboxDetailPane.tsx`): the clickable context-title button sized to its text instead of shrinking with the pane, overlapping the header controls (open-in-channel, members, huddle, more menu). Fixed by adding `max-w-full`. - **Message author names** (`MessageHeader.tsx` / `UserProfilePopover.tsx`): the `UserProfilePopover` inline-flex trigger wrapper refused to shrink below the name's nowrap width, running long author names under the hover action bar and off the pane edge. Fixed by adding a `triggerClassName` prop to `UserProfilePopover` and passing `min-w-0 max-w-full` at the author call site. Two other suspected instances (project file breadcrumb, drafts pane title) were stress-tested and already truncate correctly — no change. ### Related issue N/A — none found. ### Testing - New Playwright regression tests for both fixes (`inbox-title-overlap.spec.ts`, `message-author-overlap.spec.ts`, registered in the smoke project), each proven to discriminate: they fail with the fix reverted (real measured overlap) and assert the ellipsis actually engages with non-zero title width, so they can't pass vacuously. - Typecheck, lint, and full desktop unit suite green (pre-push hooks); full desktop e2e smoke suite run earlier: 1402 passed, 3 pre-existing unrelated failures (each fails identically with the fix reverted). **Inbox title — before** (long title paints under the header controls):  **Inbox title — after** (truncates with ellipsis, controls stay clear):  **Author name — before** (long name runs past the header row edge):  **Author name — after** (clean cutoff):  🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: cynfria <yescynthia@gmail.com> Signed-off-by: Tree Trunks <6ba22921d9dc2ad0aa6ecdf63787ddd24726e266d866da31af69f2e4e146ace5@buzz.block.builderlab.xyz> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Tree Trunks <6ba22921d9dc2ad0aa6ecdf63787ddd24726e266d866da31af69f2e4e146ace5@buzz.block.builderlab.xyz> |
||
|
|
051c3a270b |
fix(buzz-acp): report missing models without retrying (#7538)
## Summary When an agent reports model-not-found, Buzz retries the unavailable model and delays the failure reply until retries are exhausted. Stop retrying this error and immediately post a threaded recovery notice. The notice tells users to select a different model in agent settings, save, restart the agent to apply the configuration, and re-send their request. This adds one error-handling branch and regression coverage in `buzz-acp`. It matches `-32002` errors containing `model not found`. Other resource-not-found errors, such as stale sessions, retain the existing retry behavior. Detailed error events remain available for diagnosis. The existing restart policy is unchanged. ### Related issue None found in existing issue/PR searches for model-not-found recovery. ### Testing Playwright captured and visually checked the thread UI with seeded conversation data and the exact recovery text. The check opens the request's thread, confirms no reply before the failure, injects the notice, and verifies the full text is visible. [Before/after screenshots](https://github.com/block/buzz/pull/7538#issuecomment-5608196506) show the corrected save-and-restart instructions. These are local test captures, not a deployed provider recovery flow. Generated with Codex --------- Signed-off-by: Diem Nguyen <diem@squareup.com> |
||
|
|
12023a3cba |
fix(desktop): require a Codex adapter with Astra support (#7427)
## Summary Buzz considers codex-acp 1.6.2 current because the supported adapter floor is still 1.1.7. That adapter bundles Codex 0.148.0, so updating a separate Codex CLI to 0.153.4 leaves managed agents on the older runtime and unable to use GPT-6 Astra. Raise the supported adapter floor to the published 1.10.0 release, which depends on `@openai/codex ^0.153.3`. Existing discovery and installation code then classifies older adapters as outdated and offers the managed reinstall path. Update the availability and install-plan regressions to cover the observed 1.6.2 installation and the new minimum. This follows the existing version-floor policy. It does not automatically update a running installation: the user must complete Buzz’s offered adapter upgrade. Future upstream compatibility changes may require another floor update. ### Related issue No exact duplicate found in searches for Astra, CODEX_PATH, bundled Codex, outdated runtime, and codex-acp 1.10. Related: #3097 raised the older floor to 1.1.7 (already present on main); #2422 covers lost error details for runtime mismatches. Neither resolves this version gap. Originating conversation: buzz://message?channel=3286cd76-f83e-4c7d-8317-10a16580744d&id=8b79a73078217222b870fff144c27e7d27bcd5a67c966869c18fe726db716898 ### Testing - Isolated npm install of codex-acp 1.10.0 resolved bundled Codex 0.153.4, with no CODEX_PATH override. - Live macOS ACP probe: initialize protocol v1 → session/new → select gpt-6-astra[medium] → prompt. Received `OK` and `stopReason: end_turn`; usage metadata confirms gpt-6-astra. - Existing adapter 1.6.2 initialized but advertised no Astra model in the same probe. - Desktop Rust formatting and `git diff --check` pass. - `just desktop-tauri-test`: 3,266 passed, 20 ignored, zero failures across the Desktop workspace and integration tests. - Workspace and Desktop Clippy, frontend static checks, and `just file-size-check` pass. - Repository `just ci`: still running the remaining mobile/build/workspace-test stages. The installed Buzz app and managed adapter were not replaced or restarted. The live check validates the new adapter/runtime path; a complete packaged Desktop upgrade workflow remains untested. Signed-off-by: Stephen DeLorme <stephen@d.elor.me> |
||
|
|
ad9591c43b |
fix(desktop): order unnamed roster members by full canonical npub (#7503)
🤖 ## Summary - Channel members appear in the Members sidebar. A member who has never set a display name is listed under an abbreviated form of their public key (npub), and the sidebar previously sorted those unnamed members by that short label. Short labels are not unique — different keys can share one — so the order of unnamed members could look arbitrary or unstable. Unnamed members now sort by their full public key, so the order is deterministic. - When two members display the same name, the previous tiebreak was membership order (who joined first), which is not visible to a reader and can shift as roster data loads in. The tiebreak is now the full public key, so identical display names always land in the same order. - Nothing gets noisier on screen: the full key is used only for sorting, and the sidebar still shows the compact abbreviated form. Priorities are unchanged — authored (custom) names still outrank fallback labels, and role/current-user grouping still applies. - Scope is the desktop app's Members sidebar and member management: the two existing sort comparators. Mobile and other lists in the app are untouched. ### Related issue Based on #7495 (introduced the abbreviated npub labels this follows up on). The original five presentation PRs remain independently reviewable. No closer duplicate found. ### Testing - 6469 desktop unit tests, typecheck, and check pass. - The 3 existing consumer-seam E2E tests still pass; a new E2E test asserts the sidebar lists unnamed members in full-key order, with fixture members deliberately inserted in the opposite order so incoming membership order cannot mask the sort. - Negative check: reverting only this change makes the new ordering assertion fail, so it genuinely binds the new sort. - CI has not run on this PR yet. Buzz provenance: channel 1f0e4a3d-7e01-4efe-bb16-843b357f85c9 / task 340c3de9b27dbedb8453c0c7652220f9080d30fcc70a7c4f6e27fdd4fa378056 --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> |
||
|
|
82656ffea0 |
fix(mobile): standardize public-key identity display on npub (#7493)
🤖 ## Summary In the mobile app, anyone who hasn't set a display name shows up as a raw 64-character hex key (e.g. `3a5d4f9c…`) — unreadable, and unrecognizable as the same identity across screens. Profile and Settings also let you copy that raw hex. Nostr public keys have a standard readable form — `npub1…`, the same encoding other Nostr apps and our desktop app already display. This PR makes every mobile identity surface render npub instead: - **Unnamed people everywhere** — message and thread authors, reactions, typing indicators, member lists, channel details, DM headers and tiles, inbox, search, forum cards, Pulse notes and reply context, mention suggestions, and invite rows — now show a compact npub label: first 8 + last 4 characters of the full npub joined by an ellipsis (`npub1abcd…wxyz`), the same truncation desktop uses. Previously these showed truncated raw hex. - **DM fallback avatars and blank names** — 1:1 DM tiles and headers key their fallback avatar to the same non-self counterpart the label names, including self-first participant order; a self-DM keeps its hex-key-derived initial. Blank or whitespace-only display names fall back to the compact npub instead of rendering empty, while nonblank authored names render verbatim (padding included). - **Profile sheet → "Copy public key"** now copies the full canonical npub — never raw hex. When the identity string isn't a valid public key, the copy tile is disabled, so a malformed key never reaches the clipboard. - **Settings → Identity (pubkey)** displays and copies the full npub; an invalid identity reads "Identity unavailable" with copy disabled. - **Invalid identities never leak truncated raw hex** into the UI anywhere — they render a neutral "Unknown identity" label. - **Unchanged on purpose:** display names and verified handles (NIP-05 — the `name@domain` badge) still render as before. Unnamed avatars keep distinct per-key initials, derived from the underlying hex key rather than the npub — otherwise every unnamed key would render the same "N" initial. Event IDs are not public keys, so they keep their hex truncation (in Pulse's "Replying to", the parent author shows npub while an event-id fallback still shows hex). The nevent share link, private keys, and internal hex storage are untouched. Inputs that accept a key (invite/member entry) accept both hex and npub and keep working in hex internally. ### Related issue N/A. Searched open issues/PRs for npub identity display on mobile — closest related: none found. Desktop's parallel npub standardization lives in the stacked desktop PRs (#7488 foundation, #7489 controls, #7495 display surfaces); this is the independent mobile slice (based directly on `main`, not on those branches). ### Testing At exact head `5a620e420a1fd57d9d8011ac26434eed32fcf765` (base: `main` `44316ff72`; 40 files, +1,345/−154): - Full mobile suite: 2,098 tests passing (`cd mobile && flutter test`); `flutter analyze` clean; `dart format --set-exit-if-changed .` clean — the same checks CI runs. - Widget/unit coverage at production seams: compact labels and hex-keyed avatar initials for DM headers/tiles, member rows, mention suggestions, and Pulse reply context; DM fallback avatars keyed to the labeled counterpart (self-first order and self-DMs); blank/whitespace display-name npub fallback with nonblank authored labels verbatim, including the Activity inbox sender and profile-sheet heading (each with its own empty/whitespace production-seam regression); full-npub copy and disabled-copy semantics in profile and settings; invalid-key suppression; and hex↔npub input round-trips. Verified via unit and widget tests — no device/simulator validation is claimed. ### Task provenance Buzz channel: `1f0e4a3d-7e01-4efe-bb16-843b357f85c9` Task: buzz://message?channel=1f0e4a3d-7e01-4efe-bb16-843b357f85c9&id=86b34eb4bd84a1472419e9af22636c011c0fe273e3c196f967d7a36996e149b6 --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> |
||
|
|
2226b6f95b |
fix(desktop): npub identity controls across profile, agents, and workflows (#7489)
🤖 ## Summary Building on #7488's npub foundation, this PR finishes the identity display change for the controls where you actually manage people and keys: profile, settings, agent access, and workflows. Everywhere in these surfaces, an identity key shows — and copies — as its canonical npub (npub is the human-readable encoding of a Nostr public key: the compact `npub1j57...fjmv` form where space is tight, the full npub where the whole key matters), and accepts npub as input. After this change: - Profile panel: the public-key row and the managed-by / declared-owner copies show the full npub. If a key can't be encoded, you see "Unavailable" with no copy button — never a raw or partial key. - Settings: the identity card shows and copies the npub. The hosted-communities account identity derives from the bound key (`pubkey_hex`) — the same authority as the mismatch gate and hosted operations — so the display can never disagree with what the app acts on; an unusable hex falls back to a neutral label instead of rendering the unverified server npub. The connected claim and a community's Connect action require that same usable bound key to match the local one — with no usable binding the card cannot claim connected or start Connect, while the community list, linking, and delete/rebind recovery stay available. - Hosted create/onboarding: the account and device identity rows in the create flow and owner onboarding derive from the same authoritative fields (bound key / local key), with the same neutral fallback; readiness requires a usable bound key that matches the local one. - Respond-to allowlist (controls who may respond to an agent): entries can be typed or pasted as hex or npub; both spellings of the same key are recognized as one entry and dedupe. Search results, chips, and remove buttons use the compact npub. - Workflow key fields: to/from keys display as npubs in the form and save back as canonical hex. Templates like `{{trigger.author}}`, roles, and free text pass through untouched; placeholders accept both spellings. - Recipient and agent dialogs: the verify popover is npub-only (the raw-hex line is gone); denied-membership screens never show a raw key. - The Rust-side truncated display name (used for native surfaces) shows the same compact npub, so those surfaces match the web UI. Internal representation is unchanged: keys are still stored, sent, and matched as canonical 64-character hex — npub is a display and input spelling, normalized to hex at the boundary, so existing data and integrations keep working. Bound-key usability and comparison use one normalized form (trimmed, lowercased, 64 hex characters; npub rejected), so padded or mixed-case spellings of the same key match. Display names, private keys, and event IDs are untouched. ## Details - `respondToAllowlist` / `RespondToField`: npub entries normalize to canonical hex; cross-form dedupe; compact npub in rows and chips; direct-add accepts npub and stores canonical hex. - `workflowFormTypes` / `WorkflowStepCard`: hex → npub for display, npub → canonical hex on save; templates, roles, and free text pass through in both directions (roundtrip-tested). - `UserProfilePanelFields`, `ProfileSettingsCard`, `HostedCommunitiesSettingsCard`, `MembershipDenied`, `SelectedRecipientChip`, `AddAgentToChannelDialog`: npub display and copy; invalid keys → "Unavailable" with no copy; hosted identity rows derive from the bound `pubkey_hex` (create/onboarding rows from the bound and local keys), never the unverified server npub; connected/readiness/Connect gates use the same usable-bound-key predicate, and the settings Connect invocation callback re-checks it before starting. - `src-tauri/src/commands/identity.rs`: `truncated_display_name` compacts to the first 8 + last 4 characters of the npub (above a 12-char threshold), mirroring `truncateNpub`. - e2e: profile key rows and clipboard polls assert npub forms and raw-hex suppression; the display-screenshots spec pins the npub-only popover; hosted specs drive the real settings card, create flow, and onboarding rows through their real providers, and the unlinked/npub-only identity cases assert no connected claim and no Connect action. ### Related issue - Fixes: N/A. No separate issue; the related work is the stack below. - Stack: builds on #7488 (shared npub foundation), now merged; this PR is rebased onto main and stands on its own. ### Testing At head `303c90ffa` (base: main `bfc38485`; 24 files, +1125/−146): - Focused unit suites (respondToAllowlist, workflowFormTypes, hostedCommunityApi bound-key helpers) green; mutation-checked — dropping allowlist canonicalization fails the dedupe case, and dropping bound-key normalization fails the npub-in-hex and padded same-key cases. - Full desktop unit suite 6,477/6,477, `desktop-typecheck`, `desktop-check` (formatting fixed narrowly with `biome check --write` on the touched files only), and a fresh E2E build at the current head; the add-community + hosted-communities-settings specs 18/18 and onboarding integration 69/69 on a fresh dedicated port, with focused new-case runs 4+4 covering padded same-key (ready, Connect kept — no false rebind) and npub-in-hex (neutral label, recovery, no Connect) across the settings card, create flow, and first-community onboarding, plus the unlinked-account settings regression asserting Connect cannot occur. - `cargo fmt`/clippy (both feature sets) and `cargo test identity` (71 pass) passed at the earlier full-change head; since then, the only production changes in this PR's delta are the hosted identity display authority and its fail-closed bound-key gating/normalization above (base-side fixes carry #7488's receipts) — every other change is test-only. ### Task provenance Buzz channel: `1f0e4a3d-7e01-4efe-bb16-843b357f85c9` Task: buzz://message?channel=1f0e4a3d-7e01-4efe-bb16-843b357f85c9&id=86b34eb4bd84a1472419e9af22636c011c0fe273e3c196f967d7a36996e149b6 --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> |
||
|
|
ad2a84131e |
fix(desktop): npub identity displays for mention, member, and workflow surfaces (#7495)
🤖 ## Summary Every Buzz account is identified by a long public key. Before this change, when someone had no display name, surfaces fell back to inconsistent labels — mostly raw hex fragments like `abcd1234…wxyz`, sometimes a generic role label with no key — so the same person looked different from surface to surface, and nothing looked like an npub address. This PR applies the npub identity foundation from #7488 to the everyday surfaces: a person without a display name now falls back to the same compact npub everywhere — `npub1xxxx…yyyy`, the human-readable spelling of their public key (first 8 + last 4 characters of the full npub) — across messages and mentions, reactions, huddles, member and participant lists, the sidebar and channel activity, search, projects, tray, notifications, and workflow surfaces. - **Mentions and messages**: key-only mention chips render the compact npub. Pasting a copied mention back still re-binds it byte-exactly to the identity it declares, for both the new npub chips and legacy hex-truncated chips copied by older clients — wrong, missing, or tampered key qualification is rejected instead of silently degrading to plain text. - **Reactions and huddles**: huddle reaction events and the huddle roster/participants render the compact npub for unnamed participants; workflow reaction triggers describe authors with the same form. - **Members and sidebar**: channel and community member lists, add-member results and invites, the members sidebar, the channel-activity popover, search, projects (assignees/reviewers/PR panels), the tray menu, and desktop notifications all fall back to the compact npub; titles and aria labels keep the machine-readable full labels. - **Profile labels**: panel/popover display names and owner handles fall back to the compact npub (never raw hex) when there is no name; linked-event (nevent) message metadata shows the npub-shaped author fallback while the event lookup and event IDs are unchanged. - **Workflows**: author-picker secondary labels, step destination keys, and trigger-author references render compact npubs; event and blob IDs keep their existing hex compacts (they are not identities). - **Avatars stay distinct**: fallback avatars for key-only identities derive initials from the key's tail, so prefixed role labels like "Participant npub1…" no longer collapse every unnamed participant onto the same initials; people with names keep their name initials. Preserved exactly: display names and distinct avatars, internal hex keys (storage/API forms unchanged), clipboard identity roundtrips, event/blob ID compaction, private keys (no nsec path is touched), and nevent link handling. Scope: this PR changes what identity labels **display**, not identity controls — profile/settings copy controls, the respond-to allowlist, workflow key fields, and agent dialogs are the sibling slice #7489, and the shared primitives (`canonicalNpub`, `truncateNpub`, the `<PubKey>` gate, strict input parsing) come from the foundation #7488. ### Related issue - Fixes: N/A. Searched existing issues/PRs for duplicates — none found; the related work is the npub identity stack this slice belongs to. - Base/dependency: stacks on #7488 (foundation) — this PR does not stand alone on main. - #7489 is a sibling slice on the same #7488 base (profile/agent/workflow controls), not a dependency: this PR does not require #7489, and #7489 does not require this PR — both only require #7488. ### Testing At exact head `4763cbeae1dd521309755e6d61f657324cb98667` (base: `fix/desktop-npub-identity-d1a` @ `5f3a4a8111998c8aa41ad77cf66992bd1c85343c`; 71 files, +656/−189 — production +277/−136, test support +379/−53): - At this head: targeted `mentions.spec.ts` (1/1), the e2e build, typecheck, and biome — green. - 9 changed/related unit files: 100/100 green; typecheck, e2e build, biome, and px text/truncation checks clean; huddle-roster focused run green; channel-activity e2e 11/11; mutation checks confirm the fallback wiring (removing it collapses shared initials and drops fallback rows). - Known pre-existing local e2e failures, unchanged by this PR and reproduced identically at the upstream merge-base: huddle-transcription voice-menu attribution (25 pass / 1 fail) and the `workflow-local-controls` 438px caret drift. Not claimed green locally. - Update at head `236af9e6137386737e84d3a474d6bc808a704c50` (test-only follow-ups `1143af345` + `236af9e6`): the `workflow-local-controls` races were fixed in the test drivers, and the 438px diff was shown to be a stale Darwin snapshot baseline (name-row enable switch already absent and `message_posted` already MessageSquare at recording commit `9390e11c9`) and refreshed — the focused screenshot test, including keyboard/caret assertions, now passes locally (twice). The full spec was not rerun after the snapshot refresh; the huddle-transcription item above is unchanged. Label/copy text changes are asserted by the e2e specs (`mentions`, `mention-recipients`, `pubkey-display-screenshots`, `huddle-transcription`, `channel-activity-popover`, `workflow-local-controls`) rather than new screenshots; the screenshot spec pins the compact npub text forms. ### Task provenance Buzz channel: `1f0e4a3d-7e01-4efe-bb16-843b357f85c9` Task: buzz://message?channel=1f0e4a3d-7e01-4efe-bb16-843b357f85c9&id=86b34eb4bd84a1472419e9af22636c011c0fe273e3c196f967d7a36996e149b6 --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> |
||
|
|
bfc3848558 |
fix(desktop): shared npub identity foundation (canonicalNpub, PubKey gate, strict parser) (#7488)
🤖 ## Summary Identity keys in the desktop app are displayed as raw 64-character hex. A person's key shows up as something like `953d3363…` — unreadable, impossible to recognize as the same identity on another screen, and a hazard when copied by hand. Nostr (the protocol Buzz runs on) has a human-readable spelling for identity keys — the `npub1…` form — but the desktop app did not use it consistently. This is the foundation of the desktop npub changes: it adds the shared pieces every identity surface builds on, and two follow-up slices stack directly on this branch — #7489 converts the identity controls (profile, settings, allowlist, workflow key fields) and #7495 converts the everyday display surfaces (mentions, member lists, sidebar, and other name fallbacks). After this change: - The shared identity widget shows the compact npub form — `npub1j57...fjmv` — instead of a hex prefix, everywhere it renders (for example the owned-agent public-key row on a profile). Copying it puts the full npub on the clipboard. - Copy is a real interaction, verified end-to-end: both popover variants put the exact canonical npub on the actual clipboard — never the raw hex the popover also lists, never a truncation — and a portaled popover's clicks no longer steal focus from the new-DM To-field mid-copy. Pointer copy, a natural Space-then-Enter path, and inner/outer Escape are covered. - Anything that isn't a valid identity key fails neutrally: short or corrupt values — including degenerate values that technically encode to a checksum-valid npub but aren't real identity keys — show "Unavailable" with no copy button, instead of a misleading value. - Both valid npub spellings display: all-lowercase `npub1…` and all-uppercase `NPUB1…` (Bech32, npub's encoding, permits either casing) both render the same canonical lowercase npub. Mixed case is rejected by the display path as written — `canonicalNpub` and the widget don't case-normalize input — while input parsing (`parsePubkeyInput`) keeps its trim-and-lowercase normalization and accepts mixed-case npubs; both paths require the decoded payload to be exactly a 64-character identity key. - Identity-key input is strict on payload: an npub whose decoded payload isn't exactly a 64-character identity key is rejected, matching the validation the app's Rust side already applies to agent allowlists. Intentional scope boundary: only surfaces that render through the shared widget change here. Outer profile copy, settings identity cards, the respond-to allowlist, and workflow key fields still show hex — they move to npub in the controls follow-up (#7489). Nothing else changes identity representation: display names, private keys, event IDs, and the hex the app stores, sends, and matches internally are untouched; only the user-facing spelling of an identity key changes. ## Details - `desktop/src/shared/lib/pubkey.ts` — `canonicalNpub()`: strict canonical full-npub helper (64-char hex in any case, or a checksum-validated npub, returns the canonical npub; anything else returns `null`); `truncateNpub()`: the compact display form; existing exports unchanged. - `desktop/src/shared/ui/PubKey.tsx` — the shared widget's identity gate validates through `canonicalNpub`; the popover copies the npub only. - `desktop/src/shared/lib/nostrUtils.ts` — `parsePubkeyInput` rejects npubs whose payload is not exactly a 64-character identity key. - `desktop/src/features/messages/ui/NewMessageScreen.tsx` — the To-field focuses its search input only for clicks that land inside the field itself, so portaled recipient popovers keep their focus while open (a popover click previously dismissed it mid-copy). - Unit suites cover the helper, widget, and parser (including the degenerate-encode and uppercase regressions); the e2e specs that render these rows assert the npub display. ### Related issue - Fixes: N/A. Searched existing issues/PRs for npub identity display — no existing match. - Stack: #7489 is based on this branch and builds on these primitives; it does not stand alone on main. ### Testing At head `b3310c248` (base: main `44316ff72`; 12 files, +440/−39): - Focused unit suites (pubkey, PubKey, parsePubkeyInput): 20/20 green; mutation-checked — removing the decoded-length predicate fails the short/empty checksum-valid-npub assertions in `canonicalNpub` and the widget, and a wrong-identity clipboard value fails the new copy assertions. - `pnpm typecheck` and `pnpm check`: pass; full desktop unit suite 6459/6459 at this exact head. - Targeted e2e at this exact head: 8/8 across the two specs that own the clipboard flows — `agent-access-warning.spec.ts` (compact variant, agent-access owner hint) and `pubkey-display-screenshots.spec.ts` (full variant, new-DM recipient verification: pointer copy, popover surviving the copy, inner/outer Escape, Space-then-Enter). - No Rust-side or build files change in this PR, so those results are unaffected. ### Task provenance Buzz channel: `1f0e4a3d-7e01-4efe-bb16-843b357f85c9` Task: buzz://message?channel=1f0e4a3d-7e01-4efe-bb16-843b357f85c9&id=86b34eb4bd84a1472419e9af22636c011c0fe273e3c196f967d7a36996e149b6 --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> |
||
|
|
93761e411c |
fix(mobile): render push notification sender identity as npub (#7494)
🤖 ## Summary When an iOS push notification comes from someone the app has no cached name for, the notification title showed the first characters of the sender's raw public key — for example `aa4fc866…`. That fragment is unreadable and doesn't match how the same person appears anywhere else in Buzz. This PR changes that title to the compact form of the sender's npub (npub is the human-readable encoding of a Nostr public key): first 8 and last 4 characters — for example `npub14f8…9nsy`, the same identity shape used across the desktop and mobile apps. - Unnamed senders: raw hex fragment → compact npub. - Named senders: unchanged — a sender the app has a display name for still titles the notification with that name. - Unverifiable sender identities (malformed keys, or lookalike strings that are not literal 64-hex-digit keys) now render a neutral "Someone" instead of partial raw key material. - Everything else about the notification is unchanged: body text, subtitle, thread matching and grouping, deep-link navigation, thread identifiers, and the internal hex public key the resolver matches on. The native iOS notification-service package (`BuzzPushKit`) gains a minimal in-house bech32 codec (bech32 is the checksummed string encoding npubs use) — checksum-validated, 32-byte keys only, and no new external dependency. The hex input branch accepts exactly a 64 ASCII hex digit key before any parsing, so strings that merely parse like hex (for example a run of `+a` pairs) cannot become a displayed identity; this is input validation for presentation. Event signature verification is untouched. ### Related issue Fixes: N/A. Searched existing issues/PRs for push-notification npub identity — closest related: none found. ### Testing At head `3e3f2813b8864b76257ccb50dea3a4b31fa4de0d` (base `44316ff72f5f7de014c66b01cbf534298a70c249`; 4 files, +321/−4): - CI `Mobile Swift` lane, at this exact head — all passed: `swift test` (73 tests, 0 failures), the SwiftPM debug and release builds of `mobile/ios/BuzzPushKit`, and the unsigned iOS release build. - Test coverage: npub encoding cross-checked against independent nostr-rs/NIP-19 vectors; rejection of bad checksums, mixed case, wrong lengths, invalid alphabet, padding, and non-32-byte payloads; resolver boundary matrix — hex/npub/invalid sender keys render compact npub or "Someone" while body, subtitle, sender key, and thread identifier pass through; named senders keep cached display names. ### Task provenance Buzz channel: `1f0e4a3d-7e01-4efe-bb16-843b357f85c9` Task: buzz://message?channel=1f0e4a3d-7e01-4efe-bb16-843b357f85c9&id=86b34eb4bd84a1472419e9af22636c011c0fe273e3c196f967d7a36996e149b6 --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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 |  |  | | Dark |  |  | 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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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.  ### Directed unread activity The same destination count becomes primary when an offscreen room has directed activity; geometry and label remain unchanged.  ### Thread-only unread DM A DM stays primary and retains its avatar even when only its thread has unread activity.  --------- 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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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.  *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> |
||
|
|
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](
|
||
|
|
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> |
||
|
|
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.   *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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
4b0744d7f3 | test(desktop): await Bestie drag and profile hover endpoints (#7294) | ||
|
|
2ac0aa1dd1 | Collapse contiguous join messages (#7262) | ||
|
|
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> |
||
|
|
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> |
||
|
|
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> |