feat: combine Thin v6 with context-efficient ACP sessions - #8
Open
reinhold-ph wants to merge 152 commits into
Open
feat: combine Thin v6 with context-efficient ACP sessions#8reinhold-ph wants to merge 152 commits into
reinhold-ph wants to merge 152 commits into
Conversation
### Summary Fixes [this issue](buzz://message?channel=e62570dd-33ad-42c5-b92b-75f2689f9694&id=b726c366abfe62429ee3cdcd34d0c0fb98c33c3ea053480585bed71745412b56): > I often don’t see my bot responses until after I post. they’re usually time stamped correctly so I think it’s just a refresh issue? ### What changed? Buzz Mobile now reconnects relay sessions after the app has remained backgrounded beyond the existing 5-second grace period, even when the session still reports a stale `connected` state. This makes resume recovery independent of whether iOS runs the grace timer before or after delivering `resumed`. Reconnection is now based on elapsed background time rather than a direct socket-health probe. - If the app was backgrounded for at least the 5-second grace period, the socket is presumed dead and the session reconnects regardless of reported status. - If it was backgrounded for less than that, a reported `connected` status is still trusted. In the sub-5-second window the socket is either genuinely alive, which is the common case for a momentary background, or it is dead and the client ping detects it within the two-interval worst case described below. That is now a degraded-latency path, not a silent-forever path. The mobile relay socket now uses `IOWebSocketChannel.connect` with a 30-second `pingInterval`. An unanswered ping closes the Dart socket through the existing disconnect and reconnect path. Detection takes up to two ping intervals, so about 60 seconds worst case, not 30. One interval of idleness elapses and a ping is sent, then a second interval elapses with no pong and the socket closes. Any inbound pong restarts the first stage, so the clock measures idleness rather than running on a fixed cadence. ### Why? Buzz iOS can sometimes stop showing new bot or agent responses after a phone has been locked for 5 to 10 minutes. When the user later posts a message, the missing responses can appear all at once. iOS may suspend Buzz before the short delayed cleanup that would normally close its connection has a chance to run. Before this change, Buzz trusted the resulting stale healthy status on resume and skipped reconnecting, so the missing responses stayed hidden until a later post exposed the dead connection. A state-machine test with a stubbed connection reproduced this reported pattern and showed that it matches this failure mode: the failed post triggered a reconnect that fetched the missing messages. The same test also checked the other candidate explanation, the bug tracked in [block#3053](block#3053), where the relay has closed the app's subscription. That state does not produce the pattern. Posting succeeds and the user's own message appears, but nothing looks for the missed messages, so they stay hidden. The test confirmed that the missed messages were still available to fetch in that state, so the missing step was a trigger to fetch them. This was not an end-to-end reproduction on an iOS device or a live relay. The new resume check covers the normal lock and unlock path. If the app was backgrounded for less than the 5-second grace period, it still trusts a connection marked as healthy. A dead connection in that window is instead detected by the ping check, which can take up to about 60 seconds but prevents the app from remaining silently stuck. The ping only runs while iOS is running the app, so it does not detect a connection that died during suspension; the resume check owns the lock and unlock path. A pre-existing path also runs the same resume handling when network connectivity returns while the app is already in the foreground. Because the app was not backgrounded, this change does not alter that path, which still trusts a connection marked as healthy and relies on the slower ping check. Recovery from a subscription that the relay explicitly closes remains in [block#3053](block#3053), and the two changes overlap in one file. Changes to how missed messages are backfilled or replayed are out of scope. ### How is it tested? Full mobile suite at base and head. Both runs have the same known macOS-host-only failure in `ChannelDetailPage keeps follow mode off while a tall newest message stays visible` at line 1053: - Base: 1,021 passed, 1 skipped, 1 failed - Head: 1,025 passed, 1 skipped, 1 failed Added tests: - [`relay_session_test.dart`](https://github.com/block/buzz/tree/main/mobile/test/shared/relay/relay_session_test.dart): long-background resume reconnect and within-grace control - [`relay_socket_liveness_test.dart`](https://github.com/block/buzz/tree/main/mobile/test/shared/relay/relay_socket_liveness_test.dart): silent-peer disconnect and idle-but-healthy control Mutation checks confirm that removing elapsed-background resume recovery fails with one socket instead of two, and removing `pingInterval` leaves the silent peer connected. Restored production code passes both mutations' regression tests and the healthy idle control. Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: npub1tquskdu6yc4h8l7xxtceculxw600grekeq0xg2ukqfrwl7vrzg3quz3gmp <58390b379a262b73ffc632f19c73e6769ef40f36c81e642b960246eff9831222@buzz.block.builderlab.xyz>
## Summary Gate 1 only for desktop release caching: - replaces canary `rust-cache` use with explicit exact-key `actions/cache/restore` + `save` - computes keys after `cargo update --workspace`, including platform, target, Rust toolchain, Cargo manifests/locks, profile/features, and native-toolchain inputs - normalizes only the desktop package version so a trusted `main` canary can warm an otherwise identical release tag - excludes Tauri bundle directories, so installers and signed artifacts are never cached - adds a restore-only `cache-proof-*` tag workflow that fails unless tag scope sees the exact default-branch cache - adds contract tests that enforce no release-workflow cache change in Gate 1 `release.yml` is intentionally unchanged. A cache miss remains the current cold canary build; the release path cannot be affected by merging this PR. ## Validation - `scripts/test-desktop-release-cache-key.sh` - `scripts/test-desktop-release-cache-workflow.sh` - `scripts/test-release-ref-contract.sh` - Ruby YAML parse of all four changed workflows - `git diff --check` - pre-push `branch-skew` ## Post-merge proof plan 1. Run each canary cold on trusted `main`, recording cache size/save time and fresh artifact inventory. 2. Run each canary warm, requiring the exact-key hit and recording restore/build time. 3. Create a disposable `cache-proof-*` tag at that same trusted `main` SHA and dispatch **Desktop release cache tag-scope proof** from the tag. 4. Do not begin Gate 2 or modify `release.yml` unless the exact tag-scope restore succeeds and cache transfer economics are favorable. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
**Category:** improvement **User Impact:** Users can skip default model configuration during onboarding and finish it later in Settings → Agents. **Problem:** Requiring model defaults during onboarding can block users who are not ready to choose a harness, provider, or model. Skipping also needs to leave existing configuration untouched rather than persisting partial selections. **Solution:** Stage onboarding edits locally and persist them only when users choose Next or Back. A delayed Skip action advances without any configuration write, while a footer hint points users to the settings location for completing setup later. <details> <summary>File changes</summary> **desktop/src/features/onboarding/ui/DefaultConfigStep.tsx** Adds the skip action and future-settings hint, and makes model configuration transactional so Skip discards staged changes while Next and Back preserve the intended save behavior. **desktop/src/testing/e2eBridge.ts** Exposes model-config setter call counts so tests can distinguish a true zero-write skip from a write-and-rollback implementation. **desktop/tests/e2e/onboarding-agent-defaults.spec.ts** Covers skipping during loading and after staged edits, verifies zero persistence calls, and confirms Next and Back still commit changes. </details> ## Reproduction steps 1. Start fresh onboarding and continue through harness setup to **Configure your default model settings**. 2. Change the selected harness or model, then choose **Skip for now**. 3. Confirm onboarding advances to **Join or create a community** and the prior global model configuration remains unchanged. 4. Return through onboarding and confirm **Next** saves the staged selection; confirm **Back** also preserves staged changes before returning. 5. Confirm the footer says model defaults can be configured later in **Settings → Agents**. --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
## Summary - show an unambiguous `App default (10)` inherited state for parallelism in create and edit forms - explain that blank inherits the app default and suppress create-form number steppers that could silently set `1` - align the E2E mint fallback with production while preserving explicit input → definition → app-default precedence ## Why The forms displayed `1` even though an untouched field is omitted and desktop minting materializes `10`. The create-form spinner could also turn blank/inherited into an explicit `1` with one click while leaving the field looking nearly unchanged. ## Testing - `pnpm test` (desktop: 3,886 passed) - `pnpm typecheck` (desktop) - `pnpm check` (desktop) - pre-push `desktop-check` and `desktop-test` --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
**Category:** fix **User Impact:** Long custom emoji names now stay contained inside reaction popovers and remain fully readable. **Problem:** An unbroken custom emoji name could force a reaction popover beyond its intended maximum width and overflow the message view. **Solution:** Give the reaction popover a definite 288px width and allow the complete emoji name to wrap within it without truncation or ellipsis. Short names retain the same content and interaction behavior. <details> <summary>File changes</summary> **desktop/src/features/messages/ui/MessageReactions.tsx** Bounds the reaction popover width and allows long names to break across lines while preserving the full shortcode. **desktop/tests/e2e/reaction-names.spec.ts** Covers fixed width, full text preservation, and wrapping for the maximum supported colon-wrapped reaction name, with deterministic seeded Picsum visual fixtures and explicit image-load waits. </details> ## Reproduction Steps 1. Open a message with a custom emoji reaction whose name is 64 characters. 2. Hover or focus the reaction pill to open its details popover. 3. Confirm the popover remains 288px wide and the complete name wraps within it without ellipsis. 4. Open a short-name reaction and confirm its popover remains readable and unchanged in behavior. ## Screenshots | Before | After | | --- | --- | |  |  | **Short-name regression check**  ## Verification - `pnpm test` in `desktop`: 3,858 passed - Focused reaction-name E2E with seeded Picsum captures: 2 passed - Desktop checks and commit hooks passed Originating Buzz channel: `f2ec9671-d78e-4cde-894c-9f4c458c7f1f` --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
## Summary - Refresh Share Compute with the shared agent-style model controls. - Reveal sharing details and advanced options only while sharing. - Remove the preview-only mesh API path. ## Validation - `pnpm check` - `pnpm test` - `pnpm exec playwright test tests/e2e/mesh-compute.spec.ts` Snapshots are attached in a follow-up comment. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com>
…ock#4578) ## Overview The global Agent Defaults surface (Settings card, defaults modal, onboarding) exposed structured controls for Effort but left Max Output Tokens, Context Limit, and Max Rounds as raw env vars. Per-agent dialogs had structured numeric fields but only for `isBuzzAgentRuntime` — incorrectly excluding Goose. This PR unifies numeric-tuning capability across all surfaces, fixes a pre-existing dual-editor defect, and adds full test coverage. ## What changed ### Phase 1 — Catalog projection - Add `max_rounds_env_var` to `KnownAcpRuntime` in `runtime_metadata.rs` (`Some("BUZZ_AGENT_MAX_ROUNDS")` for buzz-agent, `None` elsewhere). - Project all three numeric env-var fields (`max_tokens_env_var`, `context_limit_env_var`, `max_rounds_env_var`) end-to-end: `AcpRuntimeCatalogEntry` Rust struct, TS `types.ts`, `RawAcpRuntimeCatalogEntry` + `fromRawAcpRuntimeCatalogEntry` in `tauri.ts`, and the e2e mock bridge (`withMockRuntimeConfigMetadata`). ### Phase 2 — Field model - `deriveAgentConfigFieldModel` now derives `maxOutputTokens` / `contextLimit` / `maxRounds` descriptors from catalog-projected fields. - `structuredEnvKeys(descriptors)` — exported helper that takes the **rendered** descriptor set (not the whole model). Hidden keys follow what is actually rendered per surface: global hides effort + all three numeric keys for buzz-agent / two for Goose; per-agent buzz-agent hides effort + three numeric keys; per-agent Goose hides only its two numeric keys. `BUZZ_AGENT_THINKING_EFFORT` stays a visible generic env row per-agent because no effort control renders there. ### Phase 3 — UI - Extract `NumericTuningFields` from `buzzAgentModelTuningFields.tsx` as a shared descriptor-driven component (`descriptors`, `envVars`, `inheritedEnvVars`, `onEnvVarChange`). Kind-specific minima: `NUMERIC_KIND_MIN` map (`maxOutputTokens`/`contextLimit`: 1, `maxRounds`: 0) applied to `<input min>`. - **Global surface** (`AgentConfigFields.tsx`): deduplicate the previously duplicated Advanced env-editor block; render `NumericTuningFields` below the env editor when descriptors exist; `hiddenKeys` and `bakedGenericRows` exclusions use `structuredEnvKeys` so structured keys are never double-rendered. Under 1000 lines. - **Per-agent surfaces** (`EditAgentAdvancedFields`, `PersonaAdvancedFields`): replace `isBuzzAgentRuntime` as the numeric-field gate with `deriveNumericDescriptors(selectedRuntime)` from `agentConfigCore`; hidden keys come from `structuredEnvKeys(numericDescriptors)` — the same rendered descriptor set, no local rebuilding (fixes pre-existing dual-editor defect). Catalog status carried as `RuntimeCatalogStatus` (`loading | ready | error`); both error and loading withhold structured controls and leave saved values visible as generic rows, making error distinguishable from "runtime not capable" (`ready` + no runtime). - **Dialogs** (`AgentDefinitionDialog`, `AgentInstanceEditDialog`, callers): `AgentDefinitionDialog` accepts `runtimeCatalogStatus?: "loading" | "ready" | "error"` (replaces separate `runtimesLoading`/`runtimesError` booleans); all call sites — `AgentManagementDialogs`, `AgentsView`, `RequestedAgentCreateDialogs`, `UserProfilePersonaDialogs` — compute and pass the status. ### Phase 4 — Tests - `buildRecord` exported from `EnvVarsEditor.tsx` as a pure `(nextRows, value, requiredKeys, hiddenKeys) => Record<string, string>` helper for isolation testing. - **17 new node tests** in `agentConfigCore.test.mjs`: `deriveNumericDescriptors` (all three fields, partial, undefined runtime, matches field-model subset); `structuredEnvKeys` per surface including discriminating Goose per-agent effort-key invariant; `NUMERIC_KIND_MIN` values. - **4 new node tests** in `EnvVarsEditor.test.mjs`: hidden tuning key preserved through generic row edits; runtime-switch then generic edit (derives both descriptor sets, asserts new-runtime hidden key survives `buildRecord` via `hiddenKeys` and old-runtime key survives via generic rows); baked numeric key excluded via `filterBakedGenericRows` with `numericTuningPlaceholder` assertion; clearing a structured override — `numericTuningPlaceholder` verifies placeholder text. - **5 new Playwright tests** in `agent-numeric-tuning.spec.ts` (added to smoke project `testMatch`): global numeric fields visible for buzz-agent; global: non-capable runtime hides numeric controls; Goose per-agent shows `Inherit (16384)` after saving global value through the UI; delayed catalog: saved values visible as generic rows while loading then structured controls appear after settle; failed catalog: saved values remain visible as generic rows (never the "unsupported" empty state). ## Result - buzz-agent global defaults: Max output tokens, Context limit, Max rounds as structured inputs with `Inherit (N)` placeholders from baked env. - Goose global defaults: Max output tokens, Context limit as structured inputs. - A Goose global value surfaces as `Inherit (<value>)` in the per-agent Goose edit dialog. - No structured key is editable in two places on any surface; no persisted key has zero editors. - No `runtime.id === "buzz-agent"` comparison decides numeric-field visibility anywhere — capability flows catalog → `AcpRuntimeCatalogEntry` → field model → UI. Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
## Overview **Category:** improvement **User Impact:** Mobile users can now access consistent channel and DM actions from both the channel list and conversation header. **Problem:** Mobile channel menus exposed a narrower, inconsistent set of actions than desktop, and the available actions differed by entry point. **Solution:** This change introduces one reusable action sheet with a clear quick-action hierarchy, role-aware lifecycle controls, confirmations for consequential actions, and a deliberately narrower DM menu. ## Changes <details> <summary>File changes</summary> **mobile/lib/features/channels/channel_actions_sheet.dart** Adds the shared channel and DM action-sheet experience used by both entry points, including Star/Unstar and Read/Unread quick actions for channels, section movement, mute, management, inline copy actions, guarded lifecycle actions, confirmations, and a compact DM menu without quick actions. **mobile/lib/features/channels/channel_detail_page.dart** Routes the header ellipsis through the shared action sheet so the in-channel menu matches the channel-list experience, including for DMs. **mobile/lib/features/channels/channel_management_provider.dart** Adds archive and delete operations using the desktop-compatible relay event kinds and refreshes channel state after completion. **mobile/lib/features/channels/channels_page.dart** Makes the shared channel action-sheet entry point available to the channel-list implementation. **mobile/lib/features/channels/channels_page/channel_tile.dart** Replaces the tile-specific long-press menu with the reusable action sheet while preserving read state and section context. **mobile/test/features/channels/channel_actions_sheet_test.dart** Covers action hierarchy, owner/admin/member capability guards, loading and failure states, DM narrowing with no quick-action row, and inline copy actions. **mobile/test/features/channels/channel_detail_page_test.dart** Updates channel-header flows to exercise management through the new shared action sheet. **mobile/test/features/channels/channel_management_provider_test.dart** Verifies archive and delete event tags stay compatible with desktop behavior. </details> ## Reproduction Steps 1. Run the mobile app and open a populated channel list. 2. Long-press a regular channel and verify the Star/Unstar and Read/Unread quick actions appear above Move to section…, Mute, Manage, Copy channel name, and Copy channel ID. 3. Choose either copy action and verify it copies the expected value. 4. Open a channel, tap the header ellipsis, and verify the same action sheet appears. 5. As an admin or owner, verify Archive appears; as an owner, verify Delete also appears. Confirm that lifecycle actions require confirmation. 6. Long-press or open the header menu for a DM and verify it has no quick-action row and starts with Mute, followed by Copy channel name and Copy channel ID. ## Screenshots ### Channel menu | Regular channel — Mark Unread | DM — no quick actions | Archive confirmation | |---|---|---| |  |  |  | --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
## Summary - open Huddles in a focused companion window with a clean handoff back to the in-app drawer and backing channel - redesign the participant film strip, sidebar control, transcript surface, and themed shell treatment - preserve microphone and device control across windows, start agent voice on the first reply, and show agent speaking activity in the film strip - give each agent a distinct session voice, beginning with the configured default, plus compact per-agent text-to-speech and voice controls - enroll only agents explicitly mentioned or deliberately added through an agent panel into the live Huddle roster - keep temporary Huddle channels out of the sidebar unless the user explicitly brings one into the main app - remove Huddle-only avatar policy badges and filter short silence or noise segments before speech-to-text posts ## Why The previous flow exposed the temporary channel as product UI, obscured who was present or speaking, and split transcript and audio state between the main and companion windows. This keeps backing channels as implementation details unless a user explicitly brings a Huddle into the app, while sharing the live conversation and audio lifecycle across both surfaces. Agent participants now join only after an explicit invitation, distinct voices make multi-agent Huddles easier to follow, and short microphone noise no longer becomes stray transcript messages. ## Validation - `pnpm check` - `pnpm build:e2e` - `pnpm exec playwright test tests/e2e/huddle-transcription.spec.ts --project=smoke` (13 passed) - Huddle sidebar visibility unit coverage (4 passed) - focused managed-agent and persona-mention E2E coverage (2 passed) - `pnpm test` (3,910 passed) - `cargo clippy --manifest-path desktop/src-tauri/Cargo.toml --all-targets -- -D warnings` - `cargo test --manifest-path desktop/src-tauri/Cargo.toml` (2,093 passed, 14 ignored; 3 diagnostics passed) --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
**Category:** improvement **User Impact:** Mobile readers can jump directly to their oldest unread message and return to the latest message with compact directional controls. **Problem:** Opening an active channel at its newest message makes it easy to miss where unread conversation began, while moving back through history lacks a lightweight route to the live edge. **Solution:** Capture the channel's unread boundary when it opens, offer an accessible up-chevron beneath the app bar to reach that stable target, then reveal the inverse down-chevron at the bottom whenever the reader is away from latest. Deep links retain precedence, and live-follow, pagination, composer resizing, and explicit scroll ownership continue to use the existing timeline behavior. <details> <summary>File changes</summary> **mobile/lib/features/channels/channel_detail_page.dart** Captures the channel's read state at open time and passes a stable unread snapshot into the timeline before the normal deferred read update advances it. **mobile/lib/features/channels/channel_detail_page/message_list.dart** Adds mutually exclusive oldest-unread and latest navigation, with accessible icon controls positioned at opposite edges of the message surface while preserving existing follow and deep-link behavior. **mobile/test/features/channels/channel_detail_page_test.dart** Covers the unread target, compact inverse controls, accessible tooltips, and placement beneath the frosted app bar. </details> ## Reproduction steps 1. Open a Flutter mobile channel that has unread messages without entering through a message or thread deep link. 2. Confirm an up-chevron appears directly below the channel app bar while the timeline remains at latest. 3. Tap the up-chevron and confirm the timeline scrolls to the oldest message that was unread when the channel opened. 4. Confirm the unread control is replaced by a down-chevron at the bottom of the timeline. 5. Tap the down-chevron and confirm the timeline returns to latest and resumes following new messages. ## Screenshots | At latest — up-chevron to oldest unread | Away from latest — down-chevron to latest | |---|---| |  |  | _Real iPhone 17 Pro Simulator captures from the neutral `buzz-mobile-scroll-to` channel._ Originating Buzz thread: `buzz://message?channel=5b16c478-22d8-4ddd-951a-6036e19b81ff&id=6a78af32d7ac6f531b182c4e70dd5a04c503a2dab2ce2c0c74b2c6baa5921741&thread=6a78af32d7ac6f531b182c4e70dd5a04c503a2dab2ce2c0c74b2c6baa5921741` --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
**Category:** improvement **User Impact:** Mobile users can sort each channel group by recent activity or A–Z, with their choices synchronized with desktop. **Problem:** Desktop supports persistent per-group channel sorting, but mobile shows the same groups without equivalent controls or shared preferences. The earlier mobile attempt coupled sorting to unsafe dirty-state behavior that could overwrite newer cross-client changes. **Solution:** Add mobile sorting controls and encrypted NIP-78 synchronization using the existing desktop `channel-sort` contract, while retaining ordinary whole-blob last-write-wins behavior. Local state is scoped by identity and normalized relay, startup closes fetch/subscription gaps, and both clients use the same deterministic ordering rules. <details> <summary>File changes</summary> **desktop/src/features/sidebar/lib/channelSortPreference.test.mjs** Updates ordering coverage for the deterministic, cross-client A–Z comparison rule. **desktop/src/features/sidebar/lib/channelSortPreference.ts** Aligns desktop channel-name collation with mobile so synchronized preferences produce the same visible order. **mobile/lib/features/channels/channel_sort/channel_sort_manager.dart** Adds encrypted relay synchronization with safe startup gap handling, clock checks, and ordinary last-write-wins conflicts. **mobile/lib/features/channels/channel_sort/channel_sort_provider.dart** Scopes sort state to the active identity and community lifecycle. **mobile/lib/features/channels/channel_sort/channel_sort_storage.dart** Defines the desktop-compatible payload, relay-scoped cache and migration, cleanup, and shared ordering behavior. **mobile/lib/features/channels/channels_page.dart** Connects sort state to the channel page. **mobile/lib/features/channels/channels_page/body.dart** Applies each selected order to Starred, custom groups, Channels, and DMs. **mobile/lib/features/channels/channels_page/sections.dart** Adds checked Recent and A–Z actions using the existing anchored-popover UI. **mobile/test/features/channels/channel_sort/channel_sort_manager_test.dart** Covers payload adoption, encrypted publication, conflicts, timestamps, retries, and cleanup. **mobile/test/features/channels/channel_sort/channel_sort_storage_test.dart** Covers parsing, relay isolation, migration, cleanup, and ordering modes. **mobile/test/features/channels/channels_page_test.dart** Verifies the group controls expose both choices. </details> ### Reproduction steps 1. Open the mobile channel list with populated built-in and custom groups. 2. Open a group menu and choose **Sort: Recent**; confirm active channels move to the top. 3. Choose **Sort: A–Z**; confirm deterministic alphabetical ordering returns. 4. Repeat for Starred, a custom group, Channels, and DMs. 5. Open desktop with the same identity and community and confirm each synchronized preference. 6. Switch communities and confirm cached preferences do not bleed across relays. ### Screenshots Approved `live` custom-section flow with `research` kept offscreen. | Recent selected | A–Z result | A–Z selected | |---|---|---| |  |  |  | ### Validation - Mobile `flutter analyze` — clean - Focused mobile sort and channel-page suites — 37/37 passed - Desktop full suite — 3906/3906 passed - Mobile full suite — 1034 passed, 1 skipped, 1 unrelated baseline failure reproduced at `ac4fa13b8` <!-- Originating Buzz channel: 2a16a2bb-6fd3-4d69-8182-2afcb21b2d14 --> --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
## Summary - ship **Buzz Term** end to end: the terminal engine/runtime, mounted desktop substrate, and user-visible naming - add Quinn's tape-deck-inspired banner: a beveled chassis filled by the `buzz term` wordmark, surrounded by a complete-hex field - derive the wordmark's three-stop sweep from each theme's terminal palette so primary, secondary, and accent roles remain visibly distinct across all 62 shipped themes, including light themes - paint the banner once on its own pointer-transparent canvas; PTY rendering beneath it remains unchanged ## Banner behavior - uses the renderer's shared `8.4 × 17` cell metrics and production aspect ratio `2.0238` - regenerates only for viewport/theme changes; palette switches repaint correctly while the banner is visible - dismisses on non-empty output from the active terminal session; empty output and inactive sessions do not dismiss it - fails closed below **70 columns** rather than squeezing or clipping the wordmark - adds **8 lines** to `terminalRenderer.ts` for shared cell metrics and **zero lines inside `paint()`** ## Screenshots | Buzz (light) | Buzz Dark | |---|---| |  |  | | Kanagawa Lotus (light) | Red | |---|---| |  |  | Additional production-aspect finals: [Vesper](https://buzz.block.builderlab.xyz/media/9ca6514b63f8cfb2107a85ca46f16a940c0883848e6fbc718e411af94aa13100.png), [Min Dark](https://buzz.block.builderlab.xyz/media/f67bd2970e5d64ffb07b1ae78ab58c847e6ebc23e7e7a48e067eb024dba64ec8.png), and [Dark Plus](https://buzz.block.builderlab.xyz/media/290fee08924f37d064abc687ecf3e9526ab05b87e8e56d610f23048949793dbe.png). The screenshot harness was checked against the shipped painter at this exact head: all **2,541 draw calls** matched on color, glyph, x, and y; four deliberate divergence controls fired. ## Verification at `98ebc8f9048bd5f0ceb7e843b67874d642f0b7fd` - desktop tests: **3,946 / 3,946** - TypeScript: clean - checks: pass (two pre-existing informational `useTemplate` notices only) - integration/e2e: PASS (independent exact-SHA lane; artifacts recorded in the originating Buzz thread) - artifact/dead-path sweep: clean - redteam G1–G7: PASS - all six named banner emitter-deletion mutants die - independent handwritten five-row full-wordmark fixture kills Quinn's seven-mutant battery, including a one-pixel glyph change - real `112 × 46` canvas-rect dismissal tests separately cover active non-empty, active empty, and inactive non-empty output - layer-drop and zero-draw painter mutants die; z-order and pointer-events verified - CI's `tsc && vite build` includes all three banner modules - performance at DPR 2 (worst-case measured envelope): - one-time content paint: **~0.7–0.8 ms**, paid only when the banner is built or its palette changes - busy compositor, CSS `1277 × 697`, backing `2554 × 1394`: **470–497 µs/frame** for the full banner (**2.82–2.98%** of a 60 Hz frame) - busy compositor, CSS `1920 × 1080`, backing `3840 × 2160`: **1,139–1,212 µs/frame** (**6.83–7.27%**) - empty, one-glyph, and full-banner controls converge: compositor cost follows backing-layer area and DPR rather than painted-cell count - in the actual idle welcome state, cost is below both vsync-clamped rigs' resolution; it is not claimed as zero - **Pane cross-rig spread: resolved at matched loop rate.** Two independent rigs initially differed 2.3× (58–68 vs 136 µs/Mpx of backing store; pane, CSS 1277×697 / backing 2554×1394, DPR 2). The cause of *that* spread is rAF loop rate: the higher figure came from a free-running loop at ~1600fps. Throttled to ~200–236fps, both rigs read 58–68 µs/Mpx (1.25–1.44% of a 60Hz frame). The busy-composite figures quoted above remain the **unthrottled worst case** and are conservative by ~2.3× at the pane. Not established: the mechanism and sign of free-running distortion (one rig under-charges ~15%, the other over-charges 2.3×), and the 1080p figure has not been re-measured throttled. - the layer paints only on generation/theme/resize and dismisses on first non-empty active-session output, so the measurable busy cost is a short-lived worst case rather than a persistent PTY paint-path tax ## Follow-ups in this PR These are intentionally subsequent commits after the certified static-banner head, not claims about `98ebc8f90`: 1. close the compositor metrology: remeasure the 1080p point throttled and characterize the opposite-sign free-running rAF distortion, with each measurement regime stated 2. add Tyler's animated honeycomb color waves, gated by `prefers-reduced-motion`, a full 62-theme phase-sweep contrast check, and DPR-2 per-tick performance certification 3. land the already-proven mounted theme-switch regression probe from `RESEARCH/BUZZ_TERM_G3A_PROBE/` 4. bound the slow/hang-shaped G1-c mutant `waitFor` 5. optionally trim the generator to its ink bounding box, reducing the minimum viewport from 70 to 62 columns --------- Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com> Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz> Signed-off-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz> Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz> Co-authored-by: npub1cc3ha7z055mu0rwwu7806t2wt8mj3pvu0uv5mfp2c50dahaqhczshdalg6 <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz> Co-authored-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz> Co-authored-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz> Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
## Summary - make mobile unread state visible with bold channel names, an animated Inbox badge, and swipe-to-toggle Inbox rows - add directional transitions for top-level mobile navigation - let mobile send while media uploads, with cancellable progress UI - normalize iOS and Android video uploads, attach poster frames, and improve native video playback ## Validation - `just mobile-check` - `just mobile-test` - `cargo test -p buzz-media` - Pixel smoke test - iPhone smoke test Desktop background uploads moved to block#4522 so the two platforms can be reviewed independently. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: leader <71e9f2c44a6932b6772caaaccda1911d010463c3e2c6c40410b8329956046801@buzz.block.builderlab.xyz> Co-authored-by: Tom Brow <tomb@block.xyz>
…lock#2392) (block#4374) ## What Fixes block#2392 — the action cards in the empty-channel intro ("Create agent", "Add people") had their `focus-visible` ring clipped by the surrounding scroll container. ## Root cause The cards sit in a `flex ... overflow-x-auto pb-1` row. Setting `overflow-x` (without `overflow-y`) makes the browser compute `overflow-y: auto` as well, so the container clips anything painted outside its padding box — including the cards' `focus-visible:ring-2` box-shadow. With only `pb-1` padding, the top/left/right of the ring were cut off when Tabbing to a card. ## Change `desktop/src/features/messages/ui/ChannelIntroBlock.tsx` — `pb-1` → `p-1` on the action-cards scroll container, reserving 4px on all four sides so the focus ring renders fully inside the scroll container's padding box. - 1 file, 1 line. No behavior change for mouse users or layout. ## Verification - `pnpm typecheck` — clean - `pnpm exec biome check src/features/messages/ui/ChannelIntroBlock.tsx` — clean - `pnpm check:file-sizes` — clean - Desktop unit suite — **3906/3906 pass** Signed-off-by: Sarthak Singh <sarthak.singh@juspay.in> Signed-off-by: Sarthak Singh <sarthak.singh@juspay.in>
## Summary - send desktop messages immediately while media uploads continue in background state across channel navigation - show immediate progress above the composer and keep Jump to latest above it - report the real media stages as Preparing, Processing, Converting, Uploading, and Finishing - use Buzz's shared spinner during local media work, then switch to the real percentage when byte transfer begins - animate phase-label and status-suffix changes without overlap or layout jumps - keep cancel, progress fill, message publication, and community-reset behavior coordinated with the background task - use raw Tauri IPC for large browser files so renderer-side byte serialization does not block initial feedback ## Why Desktop previously blocked sending while attachments uploaded in the composer. Large videos could also pause the renderer before progress appeared, and the progress pill said Uploading while native media processing was still underway. This makes the initial response immediate and describes the work actually happening. ## Validation - `cd desktop && pnpm check` - `cd desktop && pnpm typecheck` - `cd desktop && pnpm test` (3,931 passed) - `cd desktop && pnpm exec vite build --mode e2e` - `cd desktop && pnpm exec playwright test tests/e2e/file-attachment.spec.ts --project=smoke` (11 passed) - focused native media tests (80 passed) - native Clippy with all targets and features - pre-push native suite (2,107 passed, 14 ignored; 3 diagnostics passed) Updated phase snapshots are included in the PR comments. Split from block#4512 so the desktop and mobile changes can be reviewed independently. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
## Summary - Make channel join/leave activity use the selected inline avatar-stack treatment. - Group related membership activity for one hour and preserve profile/overflow-name interactions. - Restore the virtualized day-divider handoff and align the sticky date behavior with the message timeline. ## Validation - `pnpm check` - `pnpm test` - `cargo test --manifest-path desktop/src-tauri/Cargo.toml` - Visual desktop screenshot captured with seeded membership activity --------- Signed-off-by: kenny lopez <klopez4212@gmail.com>
## Summary - Keep the Welcome composer prompt above the dock blur so it stays readable. - Remove blur from the prompt and persona-motion paths. - Cover the crisp, correctly layered banner in the onboarding browser test. ## Validation - `pnpm -C desktop exec biome check src/features/channels/ui/WelcomeComposerBanner.tsx tests/e2e/onboarding.spec.ts` - `pnpm -C desktop build:e2e` - `pnpm -C desktop exec playwright test tests/e2e/onboarding.spec.ts --grep "finishing onboarding creates starter channels and focuses welcome-everyone for a new member" --project=integration` --------- Signed-off-by: kenny lopez <klopez4212@gmail.com>
…ty + consumer cost guidance (block#4632) Amends `docs/nips/NIP-AM.md` with three normative publisher-behavior changes per the cleared Usage v2 plan (plan v3, D4 + D2'). ## Changes ### 1. Cache emission semantics (D4) Replaces the unconditional `MAY` with qualified obligations: - Publishers SHOULD emit `cacheReadTokens` / `cacheWriteTokens` when the provider exposes a cache component. - Publishers MUST preserve an explicit zero when the provider reports zero. - Publishers MUST omit the field (never null or fabricated zero) when that component is unavailable to the publisher — including when the provider supports it but the harness does not surface it. An explicit carve-out in both the JSON comment block and the Numeric-validity prose exempts these fields from the payload-wide null guidance. Omission is the only valid representation for an unavailable cache component. ### 2. Optional `pricingIdentity` field (D2') Adds an optional, non-nullable `pricingIdentity` object (`authority`, `model`, `cacheClass`), defined as billing authority — distinct from the transport `Provider` enum. - `authority` is a registered billing-namespace identifier: exact lowercase hostname, no scheme, no path, no trailing slash. Registered values: `api.anthropic.com`, `api.openai.com`, `openrouter.ai`. The set extends only by NIP amendment. Pricing lookup is an exact string match on `(authority, model)`. - Present only when the publisher can prove applicability: direct official-endpoint connections prove via the actually-requested resolved model; other routes MUST receive response-supplied authoritative billing identity. - MUST omit for custom/overridden base URLs, gateways (unless the gateway is the named billing authority), unresolved aliases, and turns where usage contributions carry more than one billing identity (including identity-bearing mixed with unresolved). - `cacheClass` is omitted (not null) when not applicable. - `pricingIdentity` is optional but not nullable — omission is the only absence representation. - The existing `model` field retains its non-billing semantics (configured/session model) and is never overloaded. - Consumers MUST treat omission as "price unknown" and MUST NOT infer a price from the session `model` field. ### 3. Consumer cost guidance (D4) - Consumers MAY recompute cost estimates using the billing identity and a pricing manifest. - Consumers MUST retain the provenance of any cost value (e.g. `manifest-estimated`, `wire-reported`). - Consumers MUST NOT merge manifest-estimated and wire-reported costs into an unlabeled total. Manifest-vs-wire display preference is application policy and deliberately excluded from this NIP. ## Scope Doc-only. Single file: `docs/nips/NIP-AM.md`. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
## Overview Agents running in Buzz have no built-in awareness that each channel is an isolated conversation context. When a human mentions work "you" are doing in another channel, the current session can misread this as its own active context and try to coordinate, re-plan, or take ownership of it — causing confusion and wasted turns. ## What changed Added a `## Session Model` section to `crates/buzz-acp/src/base_prompt.md`, inserted immediately after the opening paragraph and before `## Buzz CLI`. The section explains: - Each channel is a separate session; multiple sessions of the same agent identity may be active simultaneously. - Sessions share core memory, workspace, and relay — but not conversation context or in-flight reasoning. - Cross-channel work belongs to the owning session by default; the current session may take it over only when the human explicitly requests it. No runtime code changes. Base prompt only. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
## Why Buzz restores cached channels and messages before profile lookups complete. On launch, that briefly exposes pubkey-derived labels in place of familiar display names. ## What - Persist a bounded, relay-scoped cache of last-known display names, NIP-01 names, and NIP-05 handles - Seed batch profile queries from those labels immediately, while keeping them stale so the existing relay request revalidates them - Keep cached data presentation-only: avatars and ownership metadata are not persisted or used to seed profile-detail caches - Remove cleared or missing profiles, purge a relay's labels when its community is removed, and include the cache in local-storage quota recovery - Add unit coverage for parsing, bounds, eviction, malformed data, and cleared profiles - Add an E2E regression that delays the relay profile response and verifies the cached name is rendered first ## Risk Assessment Low. The cache is disposable, capped at 1,000 entries per relay, scoped by normalized relay URL, and always revalidated. It contains only public label fields and does not restore avatars, agent ownership, or authorization state. ## Verification - `just ci` - `pnpm typecheck` - `pnpm test` — 3,727 passed - `pnpm exec playwright test tests/e2e/channels.spec.ts --grep "cached profile labels"` — passed Generated with Codex
## Summary - Keep selected sidebar rows regular by default; manually unread rows become bold immediately. - Apply a clearer dark-mode hierarchy: standard inactive rows at 75%, muted rows at 45%, and unread rows at full emphasis. - Keep hover text color stable while retaining the selected-row and unread cues. ## Validation - `pnpm typecheck` - `pnpm build:e2e` - Playwright: sidebar badge and channel-mute coverage ## Screenshots Posted in the PR comments. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com>
) The "Restart required" badge reports that an agent's running config has drifted from its spawn-time config, but never says what changed. This ships the full feature: a typed Rust diff engine and a TS/UI layer that renders it at every badge site. ## Rust core (spawn-snapshot diff engine) Replaces the lossy `u64` `spawn_config_hash` with a typed `SpawnConfigSnapshot`. The snapshot is stamped from the already-resolved command/env/config values immediately before `spawn()`, closing the race window where a mid-spawn config edit would suppress the badge. `SpawnConfigSnapshot::canonical()` is the single JSON projection shared by the badge and the diff. Drift is `to_value(stamped) != to_value(current)`; the diff is a generic leaf walk over those same two values, so badge-on and diff-non-empty are structurally guaranteed. Adding a snapshot field reaches the UI with no code change to the diff engine — `mutation_table_covers_every_serialized_field` fails CI if a new field arrives without a mutation row. `eligible_restart_diff(persona_orphaned, Option<TrackedSpawnState>)` returns the final vector — snapshot walk entries plus a synthetic `adapter_availability` entry. It returns empty for an orphaned instance (spawning one would fail) and for agents with no tracked spawn state (never stamped, can never have drifted). `needs_restart = !restart_diff.is_empty()` derives from that vector and nothing else. Redaction policy (`policy_for(path)`) is shared by the wire diff and the snapshot's manual `Debug` via `is_safe_to_reveal()` from `managed_agents::env_vars` as the single authority for env-key masking: | Policy | Paths | Rendering | |---|---|---| | `Text` | `system_prompt`, `team_instructions` | character counts only | | `MaskedBare` | `args`, `relay_url` | `••••`, no suffix | | `MaskedSuffix` | non-allowlisted `env.*` | `••••` + last 4 chars when longer than 8 | | `Plain` | allowlisted `env.*` (`BUZZ_AGENT_THINKING_EFFORT`, `BUZZ_AGENT_PROVIDER`, `BUZZ_AGENT_MODEL`, `DATABRICKS_HOST/MODEL`) and everything else | verbatim | Default-deny: every env key not in the explicit allowlist stays masked. `is_safe_to_reveal()` is the single allowlist authority for both the baked-env display and the diff. `restart_diff` is omitted from the wire when empty (`skip_serializing_if`). ## TypeScript / UI layer New `restartDiff.ts` module defines `RestartDiffEntry`, `RestartChange`, `JsonValue`; `tauri.ts` and `types.ts` re-export and add `restart_diff` / `restartDiff` fields (Rust omission → `restartDiff: []`). **`RestartDiffBadge`** — hover tooltip capped at 6 entries + "and N more", `asChild` span trigger (never inside a `<button>`), auto-restart blurb below the diff list (on/off variant from `autoRestartEnabled` prop; same `AUTO_RESTART_ON_BLURB` / `AUTO_RESTART_OFF_BLURB` constants shared with the Runtime-tab banner). **`RestartDiffList`** renders the full uncapped list for the Runtime-tab banner with `tooltip`/`inline` presentation variants for correct foreground in both surfaces. **`ManagedAgentRow` B4 fix** — badge moved to a sibling `div` of the row expansion button; tooltip trigger has no `button` ancestor. **`UnifiedAgentsSection`** — both badge sites render `<RestartDiffBadge>` instead of a raw `<Badge>`, with `autoRestartEnabled` threaded from `agent.autoRestartOnConfigChange`. **Side-panel fix** — `RestartDiffBadge` rendered tab-independently in the `ProfileSummaryView` hero area (was Runtime-tab only — root cause of the ~50% inconsistency Will reported). Hero badge is `self-center` in the flex column. `ProfileRuntimeTabContent` early-return checks `needsRestart` so the banner is never dropped when all other content is empty. Auto-restart blurb in the Runtime-tab banner uses the shared constants. ## Wire shape ```jsonc "restart_diff": [ { "field": "model", "change": { "kind": "value", "before": "gpt-5", "after": "claude-4" } }, { "field": "system_prompt", "change": { "kind": "text", "before_chars": 1234, "after_chars": 1410 } }, { "field": "env.OPENAI_API_KEY", "change": { "kind": "masked", "before": "••••bc12", "after": "••••xyz9" } }, { "field": "env.BUZZ_AGENT_THINKING_EFFORT", "change": { "kind": "value", "before": "medium", "after": "high" } } ] ``` `added`/`removed` occur only for dynamic-map keys; nullable struct fields always serialize as `null`; arrays are atomic leaves (`args`, never `args.0`). ## Tests **Rust** — 1902 passing: snapshot mutation coverage, diff entry serialization, allowlist-aware env masking (`allowlisted_env_key_shows_plain_value`, `allowlisted_env_key_is_case_insensitive`, `non_allowlisted_env_key_stays_masked`), `unstamped_agent_yields_no_badge_and_no_entries` (both orphan values), `summary_without_drift_omits_restart_diff_from_the_wire`, `unstamped_availability_is_not_drift`. Clippy clean, fmt clean. **TypeScript** — `needs-restart-screenshots.spec.ts`: 11 E2E cases registered in the smoke project — all three badge sites, tooltip + keyboard focus, DOM no-button-ancestor assertion, 6+1 truncation, uncapped Runtime list, unknown field humanisation, side-panel badge on default Info tab, inactive/friendly-error Runtime opening path. Consolidates [block#3652](block#3652) Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
…lock#3976) ## Problem `Command+R` (webview reload) wipes the two in-memory refs driving sidebar channel unread badges: `observedUnreadEventsByChannelRef` and `latestByChannelRef`. The boot catch-up REQ can only fetch events newer than each channel's NIP-RS frontier, so thread replies that arrived before the frontier was passively advanced (the common case) are never re-discovered. Inbox is unaffected because it rebuilds candidates from a relay feed query and checks fine-grained `thread:`/`msg:` markers. The sidebar badge path lacks an equivalent recovery mechanism. ## Solution Persist the sidebar's per-event candidate set to localStorage as a disposable, versioned projection cache (`buzz-observed-unread.v1:<relay>:<pubkey>`) and hydrate it on boot before the catch-up REQ runs. ### New files **`observedUnreadStorage.ts`** — storage module for the cache: - Keyed `buzz-observed-unread.v1:<normalizedRelayUrl>:<normalizedPubkey>` (relay-scoped to prevent cross-community leakage, matching `threadActivityStorage`) - Stores validated per-event `ObservedUnreadEvent` rows; `latestByChannel` is derived at hydration — no divergent dual aggregate - Age pruning (7d = `READ_STATE_HORIZON_SECONDS`), per-channel cap (1000), global cap (5000) across all channels in a scope bucket - Payload `updatedAt` for LRU ordering; registered in `PURE_CACHE_KEY_PREFIXES` for 2 MiB eviction budget - Field-level validation on decode; write failure is non-fatal (session-only degradation) - Snapshot-owning timers: `scheduleObservedUnreadWrite` deep-clones the events map at schedule time — a late A-scope timer can never read B's mutable refs or write under B's key **`useObservedUnreadPersistence.ts`** — hook that owns all persistence lifecycle: - Scope fence: `normalized pubkey + normalized relay` identity; `isScopeLoaded()` callback guards both projection (`rawUnread`) and every **observed-cache mutation** (`recordUnreadEvent`, `removeChannel`, `clearAll`) before touching refs or storage. Note: stale-scope calls to `markChannelRead`/`markAllChannelsRead` can still affect `forcedUnreadRef` and NIP-RS markers, which are pre-existing on `main` and deferred to the NIP-RS arc (see Deferred below). - Synchronous `pagehide` flush closes the Cmd+R timing gap (`useReloadShortcut.ts` reloads within 500ms of teardown, before the 1-second debounce fires) - Identity-reset effect: flushes old scope, resets refs, hydrates from storage, stamps loaded scope — all atomic; cleanup flushes on unmount - `clearAll` cancels the pending timer, resets both in-memory refs, and clears storage in a single transactional operation; `removeChannel` deletes the channel from both refs and replaces any pending snapshot with the current full map — never cancel-without-replacement, preserving sibling-channel events on reload - Marker-prune effect on `readStateVersion`: evaluates each retained event with `observedUnreadEventReadAt()` (the same evaluator used by the projection memo) and removes covered events, rederiving per-channel latest — never clears a whole channel for a single thread/msg marker - Returns a stable `useMemo`-wrapped API object keyed on actual deps so unrelated re-renders do not restart the catch-up REQ - `isScopeLoaded` is a `useCallback` (not a memoized boolean) — always reads the ref at call time, never stale ### Modified files **`useUnreadChannels.ts`** — hook integration: - Calls `useObservedUnreadPersistence` with all persistence wired through the returned API - `rawUnread`: `isScopeLoaded()` guard suppresses A-scope refs from projecting under B - `recordUnreadEvent`: `isScopeLoaded()` fence before touching refs; schedules a debounced write on each successful record - `markChannelRead` clearObserved path: calls `removeChannel` so the cleared state survives reload - `markAllChannelsRead`: delegates to the owner's fenced `clearAll` — the parent does not reset the observed refs directly; `clearAll` owns the transactional clear of both refs and storage, preventing a stale scope-A callback from corrupting scope B **`localStorageQuota.ts`** — registers `buzz-observed-unread.v1:` in `PURE_CACHE_KEY_PREFIXES` ## Design constraints The cache is a **disposable projection**: versioned key, read-through only, safe to delete wholesale. It does not touch `ReadStateManager`, marker semantics, or `forcedUnreadStore`. Zero overlap with the NIP-RS manual mark-read/unread protocol work in progress in another channel; migration path when that lands is "stop reading the key." ## Test coverage **`observedUnreadStorage.test.mjs`** covers storage primitives: - Key normalization, relay-scoped isolation, round-trip correctness - Age-prune and per-channel cap on read and write; global cap across channels - `deriveLatestByChannel` correctness - Thread-marker prune leaves sibling thread events persisted and lit - Scope-isolation state machine: A rows visible in A, absent in B, restored on A again; late A-scope write does not overwrite B's bucket - Malformed structures/fields, relay/pubkey isolation, quota failure degradation **`useObservedUnreadPersistence.test.mjs`** exercises the real hook via `createRoot` + `act`: - pagehide flush: event recorded within debounce window survives reload (headline regression) - Unmount with pending write flushes before teardown - `clearAll` cancels pending debounce so no resurrection after reload - `removeChannel` replaces pending snapshot so sibling channel B survives reload (two-channel repro) - Marker prune: thread and channel markers prune covered events; sibling channels survive - `isScopeLoaded` returns false before identity-reset effect commits, true after - A→B scope switch: pending A-timer is cancelled by flush, A data persisted synchronously (hydration round-trip) - Stale `clearAll` from scope A rejects after scope B loads (observed-cache scope fence) - Stale `removeChannel` from scope A rejects after scope B loads (observed-cache scope fence) - API object identity stable across unrelated re-renders (catch-up stability) **`useUnreadChannels.test.mjs`** exercises the full parent-to-owner seam with real hook mounts: - Stale `markChannelRead` from scope A does not corrupt B's observed bucket after flush - Stale `markAllChannelsRead` from scope A does not overwrite B's bucket after flush ## Deferred Issues deferred to the NIP-RS arc (`#unread-messages-ux`) or future hardening — not regressions introduced by this PR: - **Stale-scope `forcedUnreadRef` / `markContextRead` exposure**: a stale scope-A `markChannelRead` or `markAllChannelsRead` still deletes B's `forcedUnreadRef` entries and advances B's NIP-RS markers via `markContextRead` before the observed-cache fence rejects. This is pre-existing on `origin/main` (identical shape at lines 316/330). Fix requires touching `forcedUnreadStore` and marker paths — out of scope for Fix A. Deferred to the NIP-RS work. - **`isScopeLoaded` empty-scope hardening**: `isScopeLoaded()` returns `true` when `pubkey` and `relay` are empty strings (no active session). A guard could assert non-empty identity before stamping scope-loaded. Low risk in practice since the hook is only mounted after auth, but could be tightened. - **Catch-up batch scheduling**: `handleChannelMessage` and the catch-up loop each clone the full events map per event via `scheduleObservedUnreadWrite`. For channels with large backlogs this produces O(n) snapshot clones per catch-up batch. A batch-schedule API (single snapshot at end of batch) would reduce allocations. Not observable in normal use; deferred as a performance optimization. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
## Summary - Separate direct invites from link sharing with a labeled divider. - Show the generated invite URL inline with truncation and a copy control. - Use shared loading feedback and a restrained copy-status resize. ## Validation - `pnpm -C desktop exec playwright test tests/e2e/invite-link-copy.spec.ts tests/e2e/invites-settings-screenshots.spec.ts` (7 passed) --------- Signed-off-by: kenny lopez <klopez4212@gmail.com>
Replace the stale `agent_command_override` drop logic in `apply_persona_snapshot` with a three-tier canonical command resolver. ## What this fixes The old code dropped a create-time harness pin when the persona switched to a different runtime, but it had two failure modes: 1. **Preset harnesses invisible.** `known_acp_runtime_exact()` only searches `KNOWN_ACP_RUNTIMES` (builtins). Preset harnesses such as OpenClaw live in `PRESET_HARNESSES`, so the destination lookup returned `None` and the outer `if let` branch never executed — a Goose→OpenClaw persona switch left the stale Goose override in place, keeping the agent running Goose instead of OpenClaw. 2. **Pin-side canonical resolution incomplete.** The pin was resolved by `known_acp_runtime()`, which searches by id/command/alias and returns a `&KnownAcpRuntime` entry correctly. However, if the *pin* named an alias (e.g. `claude-code-acp`) and the *destination* was a preset harness absent from builtins, the outer guard still failed for the same reason as (1). The alias regression test pins the requirement that the canonical resolver must handle both sides: alias pins must be recognised and drops must fire when the destination is a known preset. ## How it works now `canonical_harness_command(input)` accepts any form a stored override can take — bare command, alias, path prefix, or runtime id — and resolves it to the harness primary command through three tiers: 1. **Builtins** — `KNOWN_ACP_RUNTIMES`, matched by id/command/alias. 2. **Static presets** — `PRESET_HARNESSES`, matched by id or normalised command. 3. **Loaded registry** — custom/preset definitions loaded at runtime. `command_for_runtime_id` (id-only input, same three tiers) replaces the two-step `known_acp_runtime_exact`/`lookup_loaded_harness_by_id` pattern in `record_agent_command`, `effective_agent_command`, and `try_record_agent_command`, adding the static preset tier so preset harnesses resolve correctly even without a warm registry. ## Changed files - `discovery/presets.rs` — `preset_command_for_id`, `command_for_runtime_id`, `canonical_harness_command` - `discovery.rs` — re-export new functions; make `normalize_command_identity` `pub(crate)`; refactor three command-resolution functions to use `command_for_runtime_id` - `custom_harnesses.rs` — `loaded_harness_registry` visibility `fn` → `pub(super)` (needed by `canonical_harness_command`) - `persona_events.rs` — replace two-step `known_acp_runtime_exact`/`known_acp_runtime` + pointer comparison with canonical-command comparison - `persona_events/stale_pin_tests.rs` (new) — four regression tests: Goose→OpenClaw drop, OpenClaw→Goose drop, claude-code-acp alias→OpenClaw drop, same-harness path keep - `persona_events/tests.rs` — `sample_record`/`sample_persona` exposed as `pub(super)` for the new test module Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1g8493u0xfsjrvflg4n08ezd7vec99mnwzlv0qgwpr9d7gvjwhuzqx59rhw <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz>
…k#4647) ## Problem `SELECT id, community_id FROM channels WHERE id = ANY($1) AND deleted_at IS NULL` is the top **Load by waits (AAS)** on the Buzz Postgres writer. Two independent causes compound, and both are fixed here. ### 1. No index can serve it `channels` is `PRIMARY KEY (community_id, id)`, and every secondary index leads with `community_id`: | Index | Columns | |---|---| | *(primary key)* | `(community_id, id)` | | `idx_channels_nip29_group` | `(community_id, nip29_group_id)` | | `idx_channels_dm_hash` | `(community_id, participant_hash)` | | `idx_channels_community_type` | `(community_id, channel_type)` | | `idx_channels_community_visibility` | `(community_id, visibility)` | | `idx_channels_created_by` | `(community_id, created_by)` | | `idx_channels_ttl_expiry` | `(ttl_deadline)` *(partial)* | The two tenant-independent lookups carry **no `community_id` predicate** — deliberately: - `Db::communities_of_channels` — `WHERE id = ANY($1) AND deleted_at IS NULL` - `Db::community_of_channel` — `WHERE id = $1 AND deleted_at IS NULL` That independence is load-bearing, not an oversight: projecting a row's *true* owning community regardless of the fetch query's `WHERE` clause is what makes `Inv_NonInterference` non-vacuous. If the fetch ever dropped its tenant scoping, this lookup would still report the real label and the checker would catch the mismatch. But a composite btree is only usable when its leading column is constrained, so neither query can use the primary key, and nothing else leads with `id`. **Both sequentially scan `channels` on every call.** ### 2. In production the result is discarded Both call sites feed `record_read_message_rows` / `record_read_by_id_rows`, which call `tracer.record(...)`. Production binds `NoopTracer` (`crates/buzz-relay/src/state.rs`), whose `record` body is empty. The existing guard tests `trace_state`, which is `Some` for every well-formed request — it only goes `None` on malformed pubkey bytes. So the scan ran on the hot read path and its output was dropped. This is the classic eager-argument bug: `log.debug("..." + expensiveCall())` with no `isDebugEnabled()` check. ### 3. Multiplied per filter The non-search call site sits **inside the phase-3 per-filter loop**, so a `REQ` carrying N filters performed N sequential scans of `channels` before responding. ## Changes **`Tracer::enabled()`** — a capability check on the trait (the `isDebugEnabled()` of this seam), defaulting to `true`. `NoopTracer` overrides it to `false`, and both emitters in `req.rs` now gate on it, skipping the trace-only DB read entirely in production. **`migrations/0027_channels_id_lookup_index.sql`** ```sql CREATE INDEX IF NOT EXISTS idx_channels_id_live ON channels (id) INCLUDE (community_id) WHERE deleted_at IS NULL; ``` - `INCLUDE (community_id)` — both queries select exactly `(id, community_id)`, so this is covering and can be served index-only. - Partial on `deleted_at IS NULL` — matches both predicates exactly, excludes soft-deleted history, and lets Postgres skip the recheck. - **Not `UNIQUE`.** `id` alone is *not* unique in this table — `command_executor.rs` documents that `community_of_channel(channel_id)` is ambiguous because the same channel id can appear under more than one community. A unique index would encode a false constraint and fail to build on any database already holding such a pair. Worth keeping the index even though fix #1 removes the production caller: it still runs under conformance, and `community_of_channel` has the same problem on its own paths. **`schema/schema.sql`** — mirrored, since a test asserts desired-state parity. ## Conformance is unchanged This is the part worth reviewing closely. Under a real tracer `enabled()` returns `true` and **every emit happens exactly as before** — the gate only skips *building* emit inputs when nothing observes them, never an emit that would otherwise have been made. The coverage-breach guard stays non-vacuous. `CountingTracer` forwards `enabled()` to its inner tracer rather than inheriting the `true` default. Both directions matter and both fail silently: - inheriting `true` over a `NoopTracer` would keep the overhead this PR removes; - hardcoding `false` over a live tracer would suppress the emits whose absence `EmitGuard` reports as `ImplBug` — masking real breaches behind expected ones. Covered by a new regression test, `counting_tracer_delegates_enabled_to_inner`, which asserts delegation in both directions. ## Verification - `cargo check -p buzz-conformance -p buzz-relay` — clean - `cargo clippy --all-targets` — clean, zero warnings - `cargo test -p buzz-conformance` — 6/6 - `cargo test -p buzz-relay --lib conformance` — 11/11 - `cargo test -p buzz-db --lib migration` — 7/7 - `just test-unit` (pre-push) — green Migration-count assertions in `crates/buzz-db/src/migration.rs` were bumped 26 → 27, with content assertions for 0027 following the existing per-migration pattern (including a guard that it never becomes `UNIQUE`). ## Open questions for reviewers 1. **Lock strategy.** Built *without* `CONCURRENTLY`, following migration 0004's precedent, because sqlx runs each migration inside a transaction and `CREATE INDEX CONCURRENTLY` cannot run in one. This takes a brief `SHARE` lock on `channels` (blocks writes, not reads) — small relative to `events`, but an operator preferring zero write-blocking can pre-build it by hand and `IF NOT EXISTS` makes the migration a no-op. I could not confirm whether sqlx 0.9 supports a `-- no-transaction` directive; if it does, that may be preferable. 2. **Diagnosis is static.** This comes from reading the source, not from `EXPLAIN` against the live database. Worth confirming with `EXPLAIN (ANALYZE, BUFFERS)` on the writer before/after — that also sizes the win by revealing the real table size and row counts. 3. **Expected impact** scales with average filters-per-`REQ`, which I did not measure. `pg_stat_statements` ordered by `total_exec_time` would confirm this query drops off the top and show whether anything else is scanning the same way. Signed-off-by: Jemiah Westerman <jemiah@squareup.com>
## Summary - replace Buzz Term's full-app takeover with a resizable bottom dock inside the channel content surface - add a discoverable channel-header button plus hide and maximize/restore controls - create PTYs lazily and keep separate, persistent terminal workspaces per channel - capture immutable channel/thread context on every terminal session ## Multiple-channel behavior The dock is a single surface, but its tabs are partitioned by channel. Switching channels swaps to that channel's sessions without terminating background PTYs; returning restores them. New tabs capture the currently visible channel/thread context. ## Verification At commit `7ca087f8e08c80528387684364a65bf4ccd6315f`: - `pnpm --dir desktop typecheck` - `pnpm --dir desktop test` — 4,129 passed - pre-push repository hooks — desktop check/test, Tauri checks, terminal Rust suites all passed --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Signed-off-by: kenny lopez <klopez4212@gmail.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Co-authored-by: kenny lopez <klopez4212@gmail.com>
…ock#4737) > Opened by Brain (agent) on behalf of @wesbillman. ## Problem Users report the desktop app doesn't reliably reconnect and can wedge in states where only CMD+R (or a full restart) restores connectivity (thread `c2205e2b` in #desktop-reconnecting). Pinky's empirical light-switch matrix (real `buzz-relay`, SIGTERM/1012 + SIGKILL × 1s/45s/3min, at `f18a9cb10`) passed 4/4 — the backoff state machine recovers cleanly from ordinary relay loss. That isolates the user-stuck states to four special cases a reload resets but the auto flow never did. ## Fixes | Gap | Change | |---|---| | **G1** — recovery rode solely on the backoff timer (max 30s), throttled by WKWebView in occluded/background windows; nothing fired on network return or wake | New `useRelayResumeTriggers`: `online`, window focus, and visibility→visible call `preconnect()` when the session is `reconnecting`/`stalled`, rate-limited to one attempt per 5s (`relayResumeTriggerPolicy.ts`). Deliberately inert for the terminal `disconnected` state. | | **G2** — any AUTH `OK false` latched the session terminal forever, though the relay also rejects for transient causes (duplicate-AUTH "already authenticated" race, ±60s clock skew, fail-closed allowlist DB errors) | New `AuthOkTracker` (`relayAuthPolicy.ts`): "already authenticated" resolves as success; transient rejections retry with normal backoff; latch only on `restricted:` or after 3 consecutive rejections. | | **G3** — an `auth-required:` CLOSED (REQ racing AUTH after reconnect) permanently deleted the live subscription with no UI signal — frozen channel while state reads "connected" | Reclassified `auth-required:` as retryable in `relayClosedPolicy.ts`. Genuinely terminal classes (`restricted:`, `invalid:`, …) still delete. Can't loop: a truly unauthenticated session latches terminal at the connection level. | | **G4** — `useRelayAutoHeal` observed the 2s-debounced connection hook, so sub-2s flaps never triggered the heal even though `resetConnection` had already rejected every in-flight query | Auto-heal now observes the raw connection-state emitter. The existing 15s heal rate-limit still guards against flap storms. | Each fix is a colocated pure-policy module + unit tests, matching the existing `relayReconnectPolicy`/`relayClosedPolicy` pattern. ## Validation - Full desktop unit suite: **4151 pass, 0 fail** (at branch tip, `pnpm -C desktop test`) - `pnpm -C desktop typecheck` and `pnpm -C desktop check` clean (file-size ratchet respected — `relayClientSession.ts` net −2 lines despite the tracker wiring) - Evidence trail: `RESEARCH/DESKTOP_RECONNECT_CMDR_GAP_AUDIT.md` (audit), `RESEARCH/DESKTOP_RECONNECT_LIGHT_SWITCH_RESULTS.md` (Pinky's matrix) ## Not covered / follow-ups - Native macOS sleep-wake was not automated (would kill the harness session); G1's focus trigger is the mechanism that covers wake in practice, but a manual sleep-wake verification on a real build is worthwhile. - G3 terminal-CLOSED classes (`restricted:` etc.) still silently delete subs with no UI signal — surfacing that is a separate UX decision. - Stall-watchdog latency (60s idle + 10s check) left unchanged; G1 triggers largely mask it. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Signed-off-by: npub1gjuws2a2dc8z2nszprtg7v6u9q7ffeah3hgl5yx45jwn7y7aqs6s5e9xj6 <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@buzz.block.builderlab.xyz> Co-authored-by: npub1yxv5wk0u0fh6dwt925wntn7h397jvteyj4r87ttcd9xae7n2t3lqqj9jmm <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@buzz.block.builderlab.xyz> Co-authored-by: npub1gjuws2a2dc8z2nszprtg7v6u9q7ffeah3hgl5yx45jwn7y7aqs6s5e9xj6 <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@buzz.block.builderlab.xyz>
## Summary - stop retrying remote read-state publishes after the local replacement blob exceeds NIP-44's 65,535-byte plaintext limit - preserve every local read marker and leave existing relay state untouched rather than truncating remote state - keep incoming remote read-state available while suppressing further invalid publishes for the manager lifetime ## Why A repaired/reconnecting relay exposed a 1,404-context read-state on iOS. The app repeatedly serialized and attempted to encrypt that structurally oversized blob while reconnect catch-up work was running, saturating Flutter's debug UI isolate and making channel navigation take roughly ten seconds. This is intentionally fail-closed and behavior-preserving: local read behavior continues, but remote publishing pauses until the manager is recreated. No protocol or persisted-data format changes. ## Verification - `flutter test` — 1,093 passed, 1 skipped - `flutter analyze` — no issues - pre-push `mobile-test` and `branch-skew` hooks passed at `0b6423c5d4d583194f0bbe69662912133b9ae1ef` - independent review by Princess Donut: no blocking findings; compatibility-safe and correctly fail-closed Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
) ## Overview Both local archive settings — "Archive my agents' observer frames" (kind 24200) and "Archive my agents' turn metrics" (kind 44200) — previously defaulted to OFF in OSS builds, controlled by build-time env vars. This had an irreversible cost: observer frames are ephemeral (not stored by the relay), so any missed events are permanently unrecoverable. This PR makes both settings default to enabled for all builds and removes the build-time flag machinery entirely. ## What changed ### Rust - `observer_archive_default_enabled()` — returns `true` unconditionally; removed `option_env!("BUZZ_DESKTOP_BUILD_OBSERVER_ARCHIVE_DEFAULT")` check and `nest_is_dev()` runtime fallback. - `agent_metric_archive_default_enabled()` — returns `true` unconditionally; removed `option_env!("BUZZ_DESKTOP_BUILD_AGENT_METRIC_ARCHIVE_DEFAULT")` check and its OSS-build test. - `build.rs` — removed both `rerun-if-env-changed` declarations (`BUZZ_BUILD_OBSERVER_ARCHIVE_DEFAULT`, `BUZZ_BUILD_AGENT_METRIC_ARCHIVE_DEFAULT`) and the two baked-env emitting blocks. ### Build / CI - `Justfile` — removed `desktop-tauri-test-compiled-flags` recipe (the dual-compile test machinery). - `.github/workflows/ci.yml` — removed the "Desktop Tauri compiled-flag verification" CI step. ### TypeScript - `useObserverArchiveSeed.ts` — removed `observerArchiveDefaultEnabled` dep from `ObserverArchiveSeedDeps` and the `policyOn` gate in `reconcileObserverArchive`; the function now unconditionally calls `mergeSaveSubscriptionKinds`. - `useAgentMetricArchiveSeed.ts` — removed `agentMetricArchiveDefaultEnabled` dep from `AgentMetricArchiveSeedDeps` and the `defaultOn` flag-check path in `maybeSeed`; the `hasExplicitChoice` guard is preserved as the sole gate against re-seeding. - `LocalArchiveSettingsCard.tsx` — removed `policy` prop, `observerPolicy` state, and `observerArchiveDefaultEnabled` fetch from `ObserverArchiveSection`; toggle is now always enabled (just `toggling` disables it); removed the stale "Always on for internal builds" copy branch; removed the `observerPolicy !== false` guard from `handleObserverToggle`. - `tauriArchive.ts` — updated JSDoc on both default-enabled functions to reflect always-true. - `e2eBridge.ts` — changed both mock defaults from `?? false` to `?? true` so E2E tests without an explicit mock override exercise the real default behavior. ### Tests - `useObserverArchiveSeed.test.mjs` — replaced `policyOn` dep with direct merge dep; updated `test_oss_policy_off_no_merge` → `test_reconcile_always_seeds_24200`; all cancellation, identity-switch, and ordering tests adapted. - `useAgentMetricArchiveSeed.test.mjs` — removed `defaultOn` dep and `test_oss_build_does_not_seed`; updated `test_internal_build_unset_seeds_*` → `test_default_enabled_*`; `hasExplicitChoice` guard tests unchanged. ## Preservation of explicit opt-outs Users who have previously toggled the setting off are unaffected: - `useAgentMetricArchiveSeed` skips seeding when `hasExplicitChoice(pubkey)` returns true (localStorage-persisted per identity). - Observer archive reconciliation now unconditionally calls `mergeSaveSubscriptionKinds`, but a user who already deleted the subscription can turn it off via the Settings toggle, which calls `removeSaveSubscriptionKind` — this is the existing explicit opt-out path, and the toggle is now always enabled (not locked by a policy flag). ## Result - No `BUZZ_BUILD_*_ARCHIVE_DEFAULT` / `BUZZ_DESKTOP_BUILD_*_ARCHIVE_DEFAULT` references remain outside CHANGELOG/history. - Desktop node tests: 4168 pass, 0 fail. - `just desktop-tauri-check`: clean. - `just desktop-tauri-test`: all pass. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
…block#5228) **Category:** fix **User Impact:** People who onboard by importing an existing key or recovering from a phone can now use "Skip for now" (and Next) on the harness setup and model config steps, instead of getting stuck. **Problem:** On the "Set up your agent harnesses" and "Configure your default model settings" onboarding steps, clicking **Skip for now** — or **Next** — did nothing for anyone who reached those steps by importing an existing key or recovering an identity from a phone. The app stayed frozen on the step. **Solution:** The onboarding state machine sets `continuingPubkeyRef` to the current pubkey on import/recovery to keep the flow on `onboarding` until setup finishes (added in block#4845). But `complete()` never cleared that ref, so once it matched the current pubkey the stage stayed pinned to `onboarding` forever — completion could never win. `complete()` now clears the ref so finishing/skipping actually settles the flow. Fresh-generated keys never set the ref, which is why first-run fresh-key skip already worked and the gap went unnoticed. <details> <summary>File changes</summary> **desktop/src/features/onboarding/machineOnboarding.ts** Clear `continuingPubkeyRef` inside `complete()` so an imported/recovered identity's "continuing" marker no longer outlives completion and pin the stage to `onboarding`. **desktop/tests/e2e/onboarding.spec.ts** Add a regression test that imports an existing key, reaches harness setup, clicks **Skip for now**, and asserts onboarding exits (reaches community onboarding). This fails without the fix. The existing skip tests only exercised the fresh-key path, which never set the ref — hence the gap. </details> ## Reproduction steps 1. Start onboarding and choose **Use an existing key** (or recover from a phone); import a key and continue to **Set up your agent harnesses**. 2. Click **Skip for now** (or **Next**). Before this change, nothing happens — the step is stuck. The same trap hits **Configure your default model settings**. 3. With this change, Skip/Next advances out of onboarding as intended. 4. Automated: `pnpm build:e2e && pnpm exec playwright test onboarding.spec.ts --project=integration -g "imported-key users can skip out of harness setup"` — passes with the fix, fails without it. ## Root cause Introduced by block#4845 (`feat(identity): recover desktop identity from a signed-in phone`), which added `continuingPubkeyRef.current === currentPubkey` as an independent condition selecting the `onboarding` stage. That guard has no off switch: `complete()` set the completion flag but never cleared the ref, so the OR'd condition kept the stage pinned. Not a revert candidate — the guard's intent (keep a just-published identity in onboarding until setup finishes) is correct; it just needed to release on completion. Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
…rride (block#5242) ## Problem Two v0.5.6-only regressions were introduced by block#4614 (the first enforced Tauri CSP): 1. **Tab-complete caret regression** — after tab-completing an @mention, #channel, or :emoji: shortcode, the cursor landed inside the inserted text instead of after the trailing space. TipTap inserts the correct text including the trailing space, but without its base stylesheet (`.ProseMirror { white-space: break-spaces }`) the trailing space collapses visually and the caret appears mid-name. 2. **Emoji picker unstyled** — the emoji-mart picker rendered as a giant unstyled layout (oversized search SVG, collapsed grid) because emoji-mart's shadow-root stylesheet injection was also blocked. Both symptoms have the same root cause. ## Root Cause Tauri's build-time asset processor scans `index.html` for inline `<style>` elements, injects a nonce token, and adds the corresponding `'nonce-…'` source to `style-src` at runtime. Per the CSP spec, **once a nonce is present in a directive, the browser ignores `'unsafe-inline'` for that directive**. `index.html` contained an inline `<style>` with the boot background color. When Tauri nonced it and injected `'nonce-…'` into `style-src`, the intended `style-src 'self' 'unsafe-inline'` became effectively `style-src 'self' 'nonce-…'` — blocking any runtime stylesheet injection not covered by a matching nonce: - TipTap's `injectCSS()` → `createStyleTag()` injecting `.ProseMirror { white-space: break-spaces; … }` - emoji-mart's shadow-root `document.createElement('style')` injection (Inline scripts follow a separate path — they are SHA-256 hashed, not nonced.) This only reproduces in packaged builds (where Tauri's custom protocol serves the HTML and enforces the policy). `tauri dev` loads from the Vite dev server and is not affected. ## Fix Move `html { background-color: #000; }` from an inline `<style>` in `index.html` to `desktop/public/boot.css`, linked via `<link rel="stylesheet">`. A linked stylesheet is not subject to Tauri's nonce injection, so `'unsafe-inline'` in `style-src` applies as declared. The `<link>` is render-blocking (same as the inline style was), so boot-flash behaviour is identical. **The production CSP string is unchanged.** This fix makes the policy apply as intended — no security properties are altered. Will's follow-up with the security team (Jordan Mecom / Eli Foster, authors of block#4614) is noted for post-ship. A Tauri-faithful CSP harness for the Vite dev path (so this class of regression is visible before a packaged build) is tracked as a separate follow-up. ## Files Changed - `desktop/index.html` — replace inline `<style>` with `<link rel="stylesheet" href="/boot.css" />` - `desktop/public/boot.css` — new file, the extracted `html { background-color: #000; }` plus rationale comment - `desktop/src-tauri/tests/csp.rs` — update comment: nonce for styles, SHA-256 for the boot script ## Testing - `just desktop-typecheck` ✅ - `just desktop-test` ✅ (4535/4535) - `just desktop-tauri-test` ✅ (all Rust tests including `csp.rs`) - Packaged validation: `pnpm tauri build --debug` completed; compiled binary bakes `style-src 'self' 'unsafe-inline'` with no nonce source injected ✅ --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
## Summary - serialize the relay error-message test with all other tests mutating the process-wide admission gate - clear its 300-second rate-limit expiry after the assertion - prevent the paused-time waiter test from observing another test's state ## Root cause `relay::tests::oversized_hint_is_capped_in_relay_error_message_string` arms the process-wide gate for 300 seconds without taking `TEST_SERIAL` or resetting it. In a parallel test run, `relay_admission::tests::concurrent_429_extends_the_window_for_parked_waiters` can observe that expiry, producing the reported `300.001s` instead of `5s`. ## Validation - focused admission suite + relay error test repeated 10 times - pre-push `desktop-tauri-checks` passed, including the full Rust workspace suite - `branch-skew` passed Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
## Buzz Desktop release v0.5.7 - **Frozen main:** `74b913cff8512c015dc6f1a7473b253fa803f954` - **Reviewed candidate:** `f167818d25dd9f03115ab907a16f07daee2ece5c` - **Previous desktop release:** `desktop-v0.5.6` - **Proposed immutable tag:** `desktop-v0.5.7` 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>
## What changed Bind the development Compose stack's published PostgreSQL, Redis, Adminer, Keycloak, MinIO, and Prometheus ports to `127.0.0.1`. ## Why Docker publishes a host port on every interface when no host address is specified. Running the development stack on a remote workstation or VPS therefore exposes its infrastructure services to that machine's public networks. Loopback bindings retain host-local development access and Docker's internal `buzz-net` connectivity without making those services Internet-reachable. ## Impact Local workflows continue using the same ports. Deliberate remote administration now requires an SSH tunnel or another trusted private-network path. ## Validation - `docker compose -f docker-compose.yml config --quiet` - Recreated the six affected services with their existing named volumes and Docker network - PostgreSQL remained healthy and retained all 54 application tables - Redis, MinIO, and Prometheus health checks passed - All affected ports were closed on the host's public IPv4 and IPv6 addresses while remaining available on loopback Origin: `buzz://message?channel=199eb7bc-3feb-484f-ae0e-4995123721ea&id=1c5bc387e86e21bb31677f56e1c862d4d9a17943bce91f8d93e825d029ce7f72` Signed-off-by: Paweł Karniej <karniej.p@gmail.com>
…starve the handoff summary (block#5248) ## Problem The handoff summarizer sends `max_tokens: 8192` (`HANDOFF_MAX_OUTPUT_TOKENS`) with no reasoning budget separation. On reasoning models, thinking tokens count against that cap: the model can spend the entire budget reasoning, length-stop with empty `content`, and `summarize()` — which only reads `content` — reports an empty summary. The handoff then degrades to lossy history truncation. Observed on deepseek-v4-flash during a terminal-bench 2.1 run (tb21-solo-3, 89 tasks): **13 consecutive handoff attempts across 5 trials failed exactly this way** (`handoff returned empty summary; truncating`), each burning ~3 minutes of full-cap reasoning, before a stochastically-short reasoning run finally fit. circuit-fibsqrt alone: 5 failures, 5 truncations, then success on attempt 6. video-processing failed its task by one frame after 3 context truncations. ## Fix `openrouter_summary_body` now grants reasoning its own equal-sized budget and excludes it from the response: - `reasoning.max_tokens = max_output_tokens` — thinking gets a dedicated budget instead of competing with the summary text - `reasoning.exclude = true` — reasoning is never in the response body; `summarize()` only reads `content` - `max_tokens = max_output_tokens * 2` — the total cap covers both budgets, so the text budget the caller asked for is actually available for text Non-reasoning endpoints ignore the `reasoning` object. Deliberately not paired with `provider.require_parameters`, for the reasons documented at `apply_openrouter_mutations` (it hard-404s valid model ids). The prior test `openrouter_summary_carries_neither_reasoning_nor_provider` asserted `reasoning` absent from the summary body — that assertion guarded against *effort-based* reasoning leaking in from config (the body is built independently of `cfg`, which is still true and still tested: `reasoning.effort` stays unset). Replaced with `openrouter_summary_budgets_reasoning_separately_and_carries_no_provider`. ## Verification - `cargo test -p buzz-agent`: 422 unit + 110 integration tests pass at bb2fedd - `cargo fmt` / `cargo clippy -p buzz-agent --all-targets`: clean - Not yet validated against a live OpenRouter reasoning endpoint — the failing scenario needs a long-context session to trigger organically. Evidence for the mechanism is from run artifacts (13/13 empty-summary length-stops on deepseek-v4-flash) and OpenRouter's documented `reasoning.max_tokens`/`reasoning.exclude` semantics. --------- Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
**Category:** improvement **User Impact:** Users can create, discover, and import agents from one consistent Add agent dialog. **Problem:** Agent creation, discovery, and import were split across a dropdown and separate dialogs, making the Add agent flow fragmented. The existing E2E suite also continued targeting the deleted dropdown after the flows were unified. **Solution:** Route the new-agent card directly into a unified dialog with dedicated Create, catalog, and Import navigation, then update the affected E2E coverage to exercise that interface and its current empty state. <details> <summary>File changes</summary> **desktop/src/features/agents/ui/AgentDefinitionDialog.tsx** Supports rendering the agent definition form inside the unified Add agent experience while retaining the standalone dialog behavior. **desktop/src/features/agents/ui/AgentDefinitionDialogShell.tsx** Adds the shared shell used to present agent-definition content consistently in embedded and standalone contexts. **desktop/src/features/agents/ui/AgentDialog.tsx** Passes the revised dialog state and close behavior through the existing agent dialog entry point. **desktop/src/features/agents/ui/AgentsView.tsx** Connects the Agents page to the unified Add agent dialog and opens newly added catalog agents in their profile panel. **desktop/src/features/agents/ui/PersonaCatalogDialog.tsx** Combines catalog browsing, agent creation, and snapshot import behind persistent navigation, including dirty-navigation confirmation. **desktop/src/features/agents/ui/UnifiedAgentsSection.tsx** Replaces the new-agent dropdown with a direct Add agent entry point and adjusts the responsive card grid. **desktop/src/features/agents/ui/personaLibraryCopy.ts** Updates catalog-facing copy for the unified experience. **desktop/src/features/agents/ui/usePersonaActions.ts** Returns the resolved local persona after catalog activation so the caller can open the added agent. **desktop/tests/e2e/agent-readiness-screenshots.spec.ts** Opens the embedded create pane directly for readiness screenshots. **desktop/tests/e2e/agents.spec.ts** Covers unified Create, catalog, and Import navigation and asserts the current shared-agent empty state. **desktop/tests/e2e/global-agent-config-screenshots.spec.ts** Updates global configuration screenshot setup for direct create-pane entry. **desktop/tests/e2e/inline-custom-harness.spec.ts** Updates custom harness setup for the embedded create form. **desktop/tests/e2e/persona-env-vars.spec.ts** Updates environment-variable and model-provider scenarios for direct create-pane entry. **desktop/tests/e2e/persona-model-combobox-screenshots.spec.ts** Updates model combobox screenshot setup for direct create-pane entry. **desktop/tests/e2e/smoke.spec.ts** Updates agent-creation smoke coverage for the unified Add agent dialog. **desktop/tests/e2e/where-to-run-config.spec.ts** Updates provider-selection coverage for the embedded create form. </details> ## Reproduction steps 1. Open the Agents page and select the new-agent card. 2. Confirm the Add agent dialog opens directly on Create without an intermediate dropdown. 3. Use the left navigation to browse shared agents and open Import. 4. Select a catalog agent and confirm the dialog closes and the added agent's profile panel opens. 5. Run the affected desktop Playwright smoke and integration specs and confirm all scenarios pass. --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
…nchmark agent rounds (block#5318) ## Problem Two failure modes from the `tb21-glm52-crusoe-1` benchmark run (GLM-5.2 solo, TB2.1) wedged or killed 13 of 89 trials without the model being at fault: 1. **Conversation poisoning on text-only endpoints.** Crusoe's serverless `crusoeai/GLM-5.2-NVFP4` rejects any request whose history contains an image with `400: ... is not a multimodal model`. The recovery machinery for exactly this case already exists — `AgentError::UnsupportedImageInput` → `replace_unsupported_images()` strips the image blocks, marks the tool result as an error, substitutes a text placeholder, and continues the turn. But classification only matched OpenRouter's 404 body (`no endpoints found that support image input`) and was only consulted on the 404 arms. The Crusoe 400 fell through to terminal `AgentError::Llm`: the image stayed in history, every subsequent call failed identically, buzz-acp rode its 10-retry ladder (~40 min), and the trial idled to budget death. Measured blast radius: **8 trials wedged, 12.7h aggregate idle-after-poison.** 2. **Bounded agent rounds in benchmark trials.** The harness default `DEFAULT_MAX_AGENT_ROUNDS = 32` ended solo trials mid-work when turns rotated (thinking-heavy models hit max_tokens rotation fast; 4 trials died this way). Benchmark trials already have a wall-clock budget as the real limit — the round cap only converts recoverable rotation into trial death. ## Fix - `is_unsupported_image_input_error()` also matches the verbatim `is not a multimodal model` body. Matcher stays deliberately tight (same doctrine as `is_context_length_error`): misclassifying a generic 400 as recoverable would mutate history for an error that removing images cannot fix. - Both status ladders — shared `post()` and `openrouter_post()` — consult it on their 400 arms and return the typed `UnsupportedImageInput` (OpenAI-compatible providers report this as 400; a BYOK/passthrough upstream can surface the provider's own 400 through OpenRouter). - Harness `DEFAULT_MAX_AGENT_ROUNDS` → `0` (unbounded — `BUZZ_AGENT_MAX_ROUNDS=0` is the agent config's documented unbounded value). Per-agent `budget.max_calls` in manifests still overrides. ## Acceptance - A 400 with the image-rejection body reaches the existing image-strip recovery path instead of wedging the session — asserted through `complete()` (covers the return path into the convergence mapper) and at the `openrouter_post` terminal, both proving single-attempt (a deterministic capability rejection must never be retried). - Ordinary 400s stay terminal `AgentError::Llm` (existing negative tests unchanged). - Benchmark trials run unbounded rounds by default; python tests updated for 0-is-legal with a negative arm at -1. ## Verification - `cargo test -p buzz-agent`: 427 + 18 + 20 + 15 + 8 + 1 + 48 passed, 0 failed (full package, 3 consecutive clean runs) - `cargo clippy -p buzz-agent --all-targets`, `cargo fmt --check`: clean - `uv run --extra dev pytest tests/` in harbor-buzz-orchestra: 35 passed - Pre-push hooks (full workspace rust-tests + desktop-tauri-checks) green on rustc 1.95.0 at head b043860 Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
## Buzz Desktop release v0.5.8 - **Frozen main:** `6a17d035f79ad582ca3f4f3cdc38d376f2c4087f` - **Reviewed candidate:** `f3de860574bb3119018b4592353e9761635aeb07` - **Previous desktop release:** `desktop-v0.5.7` - **Proposed immutable tag:** `desktop-v0.5.8` This PR may be **squash merged** after the Desktop Release Candidate check and all protected-branch checks pass. Merging authorizes publication of the exact reviewed candidate; later or unrelated changes on `main` cannot alter it. The checked-in changelog accounts for every non-merge commit in the release range. The Desktop tag points to the reviewed candidate commit, not the later squash commit. Publication remains bound to that immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
## Buzz Relay release v0.2.1 ### Changes since relay-v0.2.0: - fix(sdk): preserve self-mention p tags in message and forum event builders ([block#4975](block#4975)) ([`78c87ae20e`](block@78c87ae)) - feat(desktop): adding rich link previews to messages ([block#3818](block#3818)) ([`1922d49cb2`](block@1922d49)) - feat(relay): accept kind:30179 private managed-agent events at ingest ([block#5133](block#5133)) ([`ad923353a2`](block@ad92335)) - fix(media): require authenticated reads ([block#4610](block#4610)) ([`769ac70b74`](block@769ac70)) - feat(identity): recover desktop identity from a signed-in phone ([block#4845](block#4845)) ([`6eb65919f1`](block@6eb6591)) - ci: prove the relay-driven mesh lifecycle — discover, join, infer, deny — with real nodes ([block#3862](block#3862)) ([`38bf642fcf`](block@38bf642)) - relay: fuzz WebSocket 1012 restart-close timing on graceful drain (BUZZ_DRAIN_JITTER_MS) ([block#4542](block#4542)) ([`e14fff74d0`](block@e14fff7)) - fix(reactions): support max-length custom emoji ([block#3833](block#3833)) ([`2ea9385015`](block@2ea9385)) - fix(channels): restrict private-channel invitations ([block#4612](block#4612)) ([`efe1893dd3`](block@efe1893)) - fix(workflow): bind trigger author to the signed event ([block#4607](block#4607)) ([`885bed35ee`](block@885bed3)) - fix(git): revoke access for banned relay members ([block#4608](block#4608)) ([`997b8caaa4`](block@997b8ca)) - Define private managed agent wire protocol ([block#4593](block#4593)) ([`067c085f37`](block@067c085)) - perf(relay): index channel-id lookups and skip trace-only reads ([block#4647](block#4647)) ([`bc9e6528a7`](block@bc9e652)) - Polish mobile inbox and media flows ([block#4512](block#4512)) ([`feccf4eabc`](block@feccf4e)) - fix(git): allow deleting the default branch ([block#4297](block#4297)) ([`fc598f5f8d`](block@fc598f5)) - feat(projects): add buzz projects CLI commands (NIP-MP kind:30621) ([block#4020](block#4020)) ([`b7bb15122e`](block@b7bb151)) - perf(relay): serve relay-membership checks from the read replica ([block#4124](block#4124)) ([`ac4fa13b8e`](block@ac4fa13)) - fix(relay): allow open relays to set their NIP-11 workspace icon (kind:9033) ([block#3998](block#3998)) ([`5765fc74b7`](block@5765fc7)) - feat(relay): accept kind:30621 multi-repo projects at ingest ([block#3171](block#3171)) ([`cb9701cd30`](block@cb9701c)) - feat(relay): raise hosted community limit to five ([block#3829](block#3829)) ([`10d5a26414`](block@10d5a26)) - fix(relay): align NIP-11 max_limit with REQ ceiling ([block#3635](block#3635)) ([`23f0c26b1c`](block@23f0c26)) - feat(relay): gate kind 30178 team-catalog reads behind the shared tag ([block#3358](block#3358)) ([`114d40d9d3`](block@114d40d)) - fix(db): isolate usage metrics advisory-lock test on scratch DB ([block#3670](block#3670)) ([`dba97eecd9`](block@dba97ee)) - perf(presence): reduce heartbeat frequency ([block#3783](block#3783)) ([`bf139e8d0b`](block@bf139e8)) - feat(mesh): upgrade embedded mesh to v0.74 and harden shared compute (split 1/2 of block#3467) ([block#3741](block#3741)) ([`4933672eb4`](block@4933672)) - feat(replica): portable heartbeat-token fence with snapshot-local reader routing ([block#3268](block#3268)) ([`63496cc1d4`](block@63496cc)) - fix(git): channel binding tooling + author remediation for unbound repos ([block#3626](block#3626)) ([`788b3c002b`](block@788b3c0)) - feat: configure S3 URL addressing style ([block#3400](block#3400)) ([`7012d86d52`](block@7012d86)) - feat(tracing): correlate trace IDs in relay logs ([block#3608](block#3608)) ([`005b5b819a`](block@005b5b8)) - fix(relay): avoid subscription lock inversion ([block#3413](block#3413)) ([`22be8bb351`](block@22be8bb)) - feat(cli): add users set-status command for NIP-38 profile status ([block#3253](block#3253)) ([`60158fce3e`](block@60158fc)) - feat(relay): make Postgres pool size configurable, default 50 ([block#3191](block#3191)) ([`2ce2d71cc3`](block@2ce2d71)) - feat(tracing): add datastore tracing plumbing ([block#2760](block#2760)) ([`e94b9aeda0`](block@e94b9ae)) - feat(invites): add use-limited invite links ([block#3141](block#3141)) ([`d500c2d5cf`](block@d500c2d)) - feat(admin): show reported message content in report detail ([block#3149](block#3149)) ([`f069a85503`](block@f069a85)) - resolve findings ([block#3150](block#3150)) ([`9b0f744804`](block@9b0f744)) - Revert "fix(cli,relay): resolve agents by verified owner" ([block#3168](block#3168)) ([`a041e2d21e`](block@a041e2d)) - fix(cli,relay): resolve agents by verified owner ([block#2615](block#2615)) ([`c3084b36d9`](block@c3084b3)) - fix(security): enforce durable community ban on NIP-43 relay-admin kinds 9030-9033 ([block#3128](block#3128)) ([`e2e0079101`](block@e2e0079)) - fix(security): authorize kind:9000 role changes in both directions ([block#3017](block#3017)) ([`00ecf2cac7`](block@00ecf2c)) - feat(desktop): handle project work from Inbox ([block#3117](block#3117)) ([`c5c4f390b6`](block@c5c4f39)) - feat(relay): make per-owner community limit configurable via BUZZ_MAX_COMMUNITIES_PER_OWNER ([block#2599](block#2599)) ([`2a051a404d`](block@2a051a4)) - feat(relay): add author-only-unless-shared read gate for kind 30175 ([block#2768](block#2768)) ([`ab3af82871`](block@ab3af82)) - fix(core): block IPv6 transition SSRF targets ([block#2801](block#2801)) ([`c26bf5945d`](block@c26bf59)) - fix(workflow): bypass system proxies for webhooks ([block#2800](block#2800)) ([`60a171b19e`](block@60a171b)) - fix(audit): hash created_at at the precision Postgres stores ([block#2638](block#2638)) ([`264a56a226`](block@264a56a)) - feat(desktop): make pull request reviews actionable ([block#2510](block#2510)) ([`9081ab0ec9`](block@9081ab0)) - fix(relay): decompress gzip-encoded git smart-HTTP request bodies ([block#2670](block#2670)) ([`5ca36e7b91`](block@5ca36e7)) - fix(sharing): preserve agent/team snapshot tEXt chunks through media sanitization ([block#2438](block#2438)) ([`b096b0a15a`](block@b096b0a)) - fix(relay): send 1012 restart close to all clients on graceful drain ([block#2575](block#2575)) ([`1911c69aa2`](block@1911c69)) - fix(media): sanitize animated image uploads ([block#2524](block#2524)) ([`8f8f5fa5a4`](block@8f8f5fa)) - fix(channels): strip leading hash prefixes from names ([block#2250](block#2250)) ([`d0ab3fdb05`](block@d0ab3fd)) - feat(relay): make Redis pool size configurable, default 16 ([block#2521](block#2521)) ([`bcc3e13069`](block@bcc3e13)) - feat(desktop+acp): spawn a harness per (agent, community) pair at GUI startup — warm sockets, lazy LLM pool ([block#2122](block#2122)) ([`61cc738ee8`](block@61cc738)) - feat(media): add S3-truth per-community storage sweep ([block#2044](block#2044)) ([`bd37a4d584`](block@bd37a4d)) - feat(relay): log NIP-98 pubkey attribution on HTTP bridge requests ([block#2206](block#2206)) ([`7e34bee62c`](block@7e34bee)) - Revert "feat(relay): inventory unreachable Git objects" ([block#2275](block#2275)) ([`0fb820f9bf`](block@0fb820f)) - feat(relay): inventory unreachable Git objects ([block#2264](block#2264)) ([`3afc9dae15`](block@3afc9da)) - relay: add author_type label to buzz_events_stored_total ([block#2243](block#2243)) ([`b9f54c43fe`](block@b9f54c4)) - fix(git): make project branch workflows reliable ([block#2213](block#2213)) ([`166f27be4b`](block@166f27b)) - feat(cli): manage repository protection rules ([block#2193](block#2193)) ([`f94324598d`](block@f943245)) - feat(cli): add agents archive/unarchive/archived subcommands ([block#2173](block#2173)) ([`7d7992067b`](block@7d79920)) - fix(mobile): sanitize Android image uploads ([block#2188](block#2188)) ([`ee21da90bd`](block@ee21da9)) - fix(cli): paginate channel directory queries ([block#2181](block#2181)) ([`03fe19d603`](block@03fe19d)) - fix(mobile): image upload fails due to unstripped metadata ([block#2185](block#2185)) ([`37f15b2001`](block@37f15b2)) - perf(relay): compact Git packs before manifest limits ([block#2172](block#2172)) ([`80e0ab16b0`](block@80e0ab1)) - perf(relay): cache Git pack hydration ([block#2169](block#2169)) ([`a4d82ec722`](block@a4d82ec)) - fix(relay): bound and observe Git read operations ([block#2167](block#2167)) ([`5f7c93d9c1`](block@5f7c93d)) - relay: gate push enqueue on live leases; batch matcher pipeline (T1b/T1a-repair/T2b) ([block#2145](block#2145)) ([`e43b2d5aac`](block@e43b2d5)) - relay: add audit logging disable switch ([block#2134](block#2134)) ([`bf5acabdde`](block@bf5acab)) - relay: skip TTL deadline bump for known-permanent channels (T1a write-amp) ([block#2125](block#2125)) ([`2e936d439c`](block@2e936d4)) - fix(git): carry NIP-OA delegation in auth event ([block#2120](block#2120)) ([`c12257d57a`](block@c12257d)) - Route lag-tolerant reads to an optional Postgres read replica ([block#2084](block#2084)) ([`29c48883d3`](block@29c4888)) - fix: recover community access visibility ([block#2074](block#2074)) ([`ca384d082d`](block@ca384d0)) - feat: proxy feedback-scoped admin attachments ([block#2059](block#2059)) ([`d7f918e3cb`](block@d7f918e)) - feat: add read-only deployment moderation dashboard ([block#1999](block#1999)) ([`68e670e001`](block@68e670e)) - Bug-bash round 2: table scroll, Goose instructions, workflow mention wake ([block#2034](block#2034)) ([`64b8fea6dc`](block@64b8fea)) - Strip media metadata on clients and reject it at the relay ([block#2006](block#2006)) ([`5cfd69cb0c`](block@5cfd69c)) - [codex] Hold Git concurrency permits through streaming (BUZZ-SEC-018) ([block#1916](block#1916)) ([`7baea42abb`](block@7baea42)) - [codex] Enforce shared relay admission limits (BUZZ-SEC-019) ([block#1917](block#1917)) ([`73fc0ec6cf`](block@73fc0ec)) - [codex] Block banned actors from moderation commands (BUZZ-SEC-007) ([block#1915](block#1915)) ([`caa195ca58`](block@caa195c)) - [codex] Fix relay WebSocket admission limits ([block#1682](block#1682)) ([`d3ce971fc7`](block@d3ce971)) - feat: add invite QR and mobile direct join ([block#1957](block#1957)) ([`648cbf3610`](block@648cbf3)) - fix(join-policy): require legal consent on hosted invites ([block#1987](block#1987)) ([`2e1577f76f`](block@2e1577f)) - [codex] Prevent actor-tag UI impersonation ([block#1931](block#1931)) ([`c540ec9678`](block@c540ec9)) - Scope relay runtime state by community ([block#1658](block#1658)) ([`d52dedb06f`](block@d52dedb)) - Apply optional relay join policy across join flows ([block#1894](block#1894)) ([`6c2d667575`](block@6c2d667)) - feat(media): require auth for relay media reads ([block#1926](block#1926)) ([`f308762852`](block@f308762)) - feat(relay): add community unarchive endpoint ([block#1908](block#1908)) ([`6b9641db2b`](block@6b9641d)) - feat(relay): gate Git web GUI separately ([block#1901](block#1901)) ([`34dc7dec75`](block@34dc7de)) - mesh: upgrade runtime, enforce membership, add shared compute provider ([block#1656](block#1656)) ([`54638ff4bb`](block@54638ff)) - Route Git scratch through configured volume ([block#1884](block#1884)) ([`2318b3096c`](block@2318b30)) - feat(relay): gate usage metrics behind stable leader ([block#1814](block#1814)) ([`59e9821503`](block@59e9821)) - Relay mesh: cross-pod tunnel + huddle transport (buzz-relay-mesh) ([block#1670](block#1670)) ([`ccb021d713`](block@ccb021d)) - feat(push): deliver accepted relay events as wakes ([block#1866](block#1866)) ([`bffbc5f22c`](block@bffbc5f)) - fix(db): resolve duplicate migration version ([block#1863](block#1863)) ([`08ad38a07f`](block@08ad38a)) - Add private product feedback sidecar ([block#1857](block#1857)) ([`af190c93e1`](block@af190c9)) - feat(relay): add durable community archival ([block#1834](block#1834)) ([`2b15a72675`](block@2b15a72)) - feat(push): add public APNs gateway ([block#1770](block#1770)) ([`1c006822e4`](block@1c00682)) - feat(relay): add atomic community ownership transfer ([block#1845](block#1845)) ([`52e42ccb9f`](block@52e42cc)) - Bound NIP-RS retention and search indexing ([block#1771](block#1771)) ([`1b4703021d`](block@1b47030)) - Add optional standalone pairing relay to Helm chart ([block#1799](block#1799)) ([`9b47c8548f`](block@9b47c85)) - fix(relay): publish membership snapshot on provisioning ([block#1761](block#1761)) ([`0950d392b7`](block@0950d39)) - feat(relay): per-community usage metrics ([block#1723](block#1723)) ([`620822899a`](block@6208228)) - refactor(desktop): remove vestigial MCP toolsets config ([block#1776](block#1776)) ([`dfec75b3c0`](block@dfec75b)) **To release:** merge this PR. The tag and build will happen automatically. Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…ock#5324) The Prompt Context modal (observer feed → check icon under sent messages) was clipping all content and card right-padding at the dialog edge. **Root cause**: `PromptContextDialog` renders inside `DialogContent`, which is a CSS grid. The child flex wrapper had default `min-width: auto`, so the widest unbreakable token in the content (64-char hex event IDs, `Tags: [[...]]` JSON) set the grid track width, blowing it past `max-w-xl`. `overflow-hidden` then clipped everything at the dialog edge — including the section cards' right padding. **Fix**: - `AgentSessionTranscriptList.tsx`: add `min-w-0` to the `flex max-h-[85vh] flex-col` wrapper so the grid item can shrink below its max-content width. - `PromptSectionAccordion.tsx`: replace `wrap-break-word` with `wrap-anywhere` on the body text (open and collapsed states) and the title. `overflow-wrap: anywhere` reduces min-content width, which `break-word` does not, letting long tokens wrap inside the cards rather than inflating the track. The `line-clamp-2` collapsed preview is preserved unchanged. Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
…ck#5330) ## Problem The `WelcomeComposerGuidanceLayer` in the `#Welcome` channel was positioned with `absolute inset-x-0 bottom-full z-[-1]` — outside the `composerWrapperRef` measurement boundary. `useComposerHeightPadding` observes `composerWrapperRef`'s block size to set `paddingBottom` on the timeline scroll container, but the absolutely-positioned layer didn't contribute to that size. The banner sat directly on top of the newest message, blocking the thread affordance on that message, and had no manual dismiss control. ## Fix **Overlap**: Changed `WelcomeComposerGuidanceLayer` from `absolute inset-x-0 bottom-full z-[-1]` to `relative` (in normal flow). As a normal-flow child of `composer-dock`, the layer's full height is now measured by the ResizeObserver and fed into the timeline's `paddingBottom`, so the newest message is always fully visible and its thread affordance is always clickable while the banner shows. **Dismiss**: Added an `X` close button (`data-testid="welcome-composer-dismiss-button"`) on the prompt state. Clicking fires `onDismiss`, which drives `dismissing → hidden` immediately (same slide-down animation as the auto-dismiss path) and marks the channel ID as completed in the session ref so the banner does not reappear on channel re-entry within the session. **Refactor**: Extracted the banner state machine (refs, timers, `useEffect`s, and callbacks) from `ChannelPane.tsx` into `useWelcomeComposerBanner.ts`. This keeps `ChannelPane.tsx` well under the 1000-line file-size ratchet and makes the state machine independently testable. ## Changed files - `desktop/src/features/channels/ui/WelcomeComposerBanner.tsx` — `WelcomeComposerGuidanceLayer` positioning fix; `onDismiss` prop; dismiss button; `overflow-hidden` / `mb-0` / `flex-1` cleanup - `desktop/src/features/channels/ui/ChannelPane.tsx` — remove inline banner state machine, use `useWelcomeComposerBanner` hook, pass `onDismiss` - `desktop/src/features/channels/ui/useWelcomeComposerBanner.ts` — new hook owning all banner state --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Hayt <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz>
## Problem
A provider can return HTTP 200 with a **truncated JSON body** — cleanly
closed connection, correct framing, content cut off mid-value. Both LLM
HTTP loops treated this as a terminal error on the first attempt:
`AgentError::Llm("json: EOF while parsing a value")`, surfaced as code
-32000 at the ACP boundary, killing the agent turn before it produced
anything.
Observed live in a tb2.1 bench trial (write-compressor, tb21-twins-1):
deepseek via OpenRouter returned a truncated body, the agent died
mid-prompt with 0 turns completed, and the trial scored 0 on a provider
hiccup.
Meanwhile the same loops already retry timeouts, 429s, 5xxs, 499s, and
mid-body stream stalls — a truncated-but-complete body was the one
transient upstream fault that fell through to terminal.
## Fix
In both `post()` and `openrouter_post()`
(`crates/buzz-agent/src/llm.rs`): when the fully-received success body
fails `serde_json::from_slice`, `continue` the **existing** retry loop
instead of returning terminal — same `MAX_RETRIES` (3) bound, same
`backoff_with_jitter`. On exhaustion, the error goes through
`terminal_llm_error` so it carries cumulative duration + attempt count
like every other retried failure (previously the `json:` error carried
neither).
`post_anthropic` routes through `post()`, so
Anthropic/OpenAI/Databricks/mesh and OpenRouter are all covered.
## Why this cannot re-run a tool call
Hard requirement: tool calls are not idempotent, and this change must
not introduce any possibility of replaying one.
1. **The retry lives inside the HTTP POST helper, below the parse
boundary.** Tool calls are only ever extracted from a *successfully
parsed* response value
(`parse_openai`/`parse_anthropic`/`parse_responses`, all downstream of
these helpers' `Ok` return). A malformed body never parses, therefore no
tool call was ever extracted from it, therefore nothing downstream of it
ever dispatched.
2. **What is re-sent is the completion request itself** — the identical
`body_bytes` captured once at function entry. Sending a completion
request executes no tools; it asks the model for the next message.
3. **Same safety class as existing behavior.** The loop already re-sends
this identical request on 429/5xx/timeout/stream-stall; this adds one
more transient-fault arm to the same loop with the same bytes.
## Tests
Three new tests mirroring the existing 499/dropped-connection fixtures
(raw `TcpListener` stubs):
- `post_retries_malformed_json_body_and_succeeds` — truncated 200 body
on attempt 1, valid JSON on attempt 2; asserts success and **exactly 2**
server-side requests
- `post_exhausts_retries_on_persistent_malformed_json` —
always-truncated body; asserts exactly `MAX_RETRIES` attempts and a
terminal error carrying `json:` + cumulative/attempt context
- `openrouter_post_retries_malformed_json_body_and_succeeds` — same
recovery through OpenRouter's separate loop
Full `cargo test -p buzz-agent` green at e7a5d7b (430 lib + all
integration targets, 0 failures); `cargo fmt` + `clippy --all-targets`
clean.
Originating conversation: buzz-benchmarking channel, thread 397a992d.
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
## Summary - remove the complete Welcome guidance surface when dismissal reaches `hidden` - preserve dismissal across the private and starter Welcome channels for the active identity - assert the starter channel's actual `welcome-everyone` title on re-entry ## Why PR block#5330 introduced two deterministic Desktop E2E failures: - the inner banner unmounted, but `welcome-composer-guidance-layer` remained - the re-entry test expected case-sensitive `Welcome` while navigating to `welcome-everyone` The state hook also scoped completion to channel IDs while `ChannelPane` remounts during navigation. The Welcome guidance is one experience spanning both Welcome channels, so completion now survives that remount while remaining identity-scoped. ## Validation At `b577eb42edffe889f63566f2457eacea720f3593`: - `pnpm -C desktop typecheck` - focused Biome check for all four changed files - E2E build - both `welcome-everywhere banner` integration tests repeated three times: **6/6 passed** - mandatory pre-push desktop check, typecheck, and full desktop unit suite: **4,535 passed** - `git diff --check` Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [clap](https://redirect.github.com/clap-rs/clap) | dependencies | patch | `4.6.1` → `4.6.6` | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/1) for more information. --- ### Release Notes <details> <summary>clap-rs/clap (clap)</summary> ### [`v4.6.6`](https://redirect.github.com/clap-rs/clap/compare/clap_complete-v4.6.5...clap_complete-v4.6.6) [Compare Source](https://redirect.github.com/clap-rs/clap/compare/v4.6.5...v4.6.6) ### [`v4.6.5`](https://redirect.github.com/clap-rs/clap/compare/clap_complete-v4.6.4...clap_complete-v4.6.5) [Compare Source](https://redirect.github.com/clap-rs/clap/compare/v4.6.4...v4.6.5) ### [`v4.6.4`](https://redirect.github.com/clap-rs/clap/blob/HEAD/CHANGELOG.md#464---2026-07-21) [Compare Source](https://redirect.github.com/clap-rs/clap/compare/v4.6.3...v4.6.4) ##### Internal - Update to syn v3 ### [`v4.6.3`](https://redirect.github.com/clap-rs/clap/blob/HEAD/CHANGELOG.md#463---2026-07-20) [Compare Source](https://redirect.github.com/clap-rs/clap/compare/v4.6.2...v4.6.3) ##### Fixes - *(derive)* Allow `"literal".function()` as attribute values ### [`v4.6.2`](https://redirect.github.com/clap-rs/clap/blob/HEAD/CHANGELOG.md#462---2026-07-15) [Compare Source](https://redirect.github.com/clap-rs/clap/compare/v4.6.1...v4.6.2) ##### Fixes - *(help)* Say `alias` when there is only one </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/block/buzz). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4xMi4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [async-compression](https://redirect.github.com/Nullus157/async-compression) | dependencies | patch | `0.4.42` → `0.4.43` | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/1) for more information. --- ### Release Notes <details> <summary>Nullus157/async-compression (async-compression)</summary> ### [`v0.4.43`](https://redirect.github.com/Nullus157/async-compression/releases/tag/async-compression-v0.4.43) [Compare Source](https://redirect.github.com/Nullus157/async-compression/compare/async-compression-v0.4.42...async-compression-v0.4.43) ##### Other - Fix hang when decoding a corrupt subsequent zstd frame ([#​470](https://redirect.github.com/Nullus157/async-compression/pull/470)) </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/block/buzz). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4xMi4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [diffy](https://redirect.github.com/bmwill/diffy) | dependencies | patch | `0.5.0` → `0.5.1` | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/1) for more information. --- ### Release Notes <details> <summary>bmwill/diffy (diffy)</summary> ### [`v0.5.1`](https://redirect.github.com/bmwill/diffy/blob/HEAD/CHANGELOG.md#051---2026-07-18) [Compare Source](https://redirect.github.com/bmwill/diffy/compare/0.5.0...0.5.1) ##### Fixed - [#​85](https://redirect.github.com/bmwill/diffy/pull/85) Merge conflict markers are now always placed on their own lines. Previously, a conflicting hunk at the end of a file without a trailing newline glued the next marker onto its last content line, producing unparseable output. This matches `git merge-file --diff3` behavior. </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/block/buzz). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4zLjIiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This PR contains the following updates: | Package | Type | Update | Change | Pending | |---|---|---|---|---| | [async-trait](https://redirect.github.com/dtolnay/async-trait) | dependencies | patch | `0.1.89` → `0.1.91` | `0.1.92` | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/1) for more information. --- ### Release Notes <details> <summary>dtolnay/async-trait (async-trait)</summary> ### [`v0.1.91`](https://redirect.github.com/dtolnay/async-trait/compare/0.1.90...0.1.91) [Compare Source](https://redirect.github.com/dtolnay/async-trait/compare/0.1.90...0.1.91) ### [`v0.1.90`](https://redirect.github.com/dtolnay/async-trait/releases/tag/0.1.90) [Compare Source](https://redirect.github.com/dtolnay/async-trait/compare/0.1.89...0.1.90) - Update to syn 3 </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/block/buzz). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4xMi4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [arc-swap](https://redirect.github.com/vorner/arc-swap) | dependencies | patch | `1.9.1` → `1.9.2` | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/1) for more information. --- ### Release Notes <details> <summary>vorner/arc-swap (arc-swap)</summary> ### [`v1.9.2`](https://redirect.github.com/vorner/arc-swap/blob/HEAD/CHANGELOG.md#192) - Document RefCnt must not panic ([#​208](https://redirect.github.com/vorner/arc-swap/issues/208)). </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/block/buzz). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4zLjIiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [anyhow](https://redirect.github.com/dtolnay/anyhow) | dependencies | patch | `1.0.103` → `1.0.104` | | [anyhow](https://redirect.github.com/dtolnay/anyhow) | workspace.dependencies | patch | `1.0.103` → `1.0.104` | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/1) for more information. --- ### Release Notes <details> <summary>dtolnay/anyhow (anyhow)</summary> ### [`v1.0.104`](https://redirect.github.com/dtolnay/anyhow/releases/tag/1.0.104) [Compare Source](https://redirect.github.com/dtolnay/anyhow/compare/1.0.103...1.0.104) - Update `syn` dev-dependency to version 3 </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/block/buzz). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4zLjIiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | Type | Update | |---|---|---|---|---|---| | [@isomorphic-git/lightning-fs](https://redirect.github.com/isomorphic-git/lightning-fs) | [`4.6.2` → `4.6.3`](https://renovatebot.com/diffs/npm/@isomorphic-git%2flightning-fs/4.6.2/4.6.3) |  |  | dependencies | patch | | [@vitejs/plugin-react](https://redirect.github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#readme) ([source](https://redirect.github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react)) | [`6.0.3` → `6.0.5`](https://renovatebot.com/diffs/npm/@vitejs%2fplugin-react/6.0.3/6.0.5) |  |  | devDependencies | patch | | [@vitejs/plugin-react](https://redirect.github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#readme) ([source](https://redirect.github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react)) | [`6.0.3` → `6.0.5`](https://renovatebot.com/diffs/npm/@vitejs%2fplugin-react/6.0.3/6.0.5) |  |  | dependencies | patch | | [dorny/paths-filter](https://redirect.github.com/dorny/paths-filter) | `v4.0.2` → `v4.0.3` |  |  | action | patch | | [isomorphic-git](https://isomorphic-git.org/) ([source](https://redirect.github.com/isomorphic-git/isomorphic-git)) | [`1.38.7` → `1.38.10`](https://renovatebot.com/diffs/npm/isomorphic-git/1.38.7/1.38.10) |  |  | dependencies | patch | | [postcss](https://postcss.org/) ([source](https://redirect.github.com/postcss/postcss)) | [`8.5.19` → `8.5.26`](https://renovatebot.com/diffs/npm/postcss/8.5.19/8.5.26) |  |  | devDependencies | patch | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/1) for more information. --- ### Release Notes <details> <summary>isomorphic-git/lightning-fs (@​isomorphic-git/lightning-fs)</summary> ### [`v4.6.3`](https://redirect.github.com/isomorphic-git/lightning-fs/releases/tag/v4.6.3) [Compare Source](https://redirect.github.com/isomorphic-git/lightning-fs/compare/v4.6.2...v4.6.3) ##### Bug Fixes - IDB interface ([#​127](https://redirect.github.com/isomorphic-git/lightning-fs/issues/127)) ([035e472](https://redirect.github.com/isomorphic-git/lightning-fs/commit/035e4725b9e6aa72d10cadc5ace20dec7ac76afb)) </details> <details> <summary>vitejs/vite-plugin-react (@​vitejs/plugin-react)</summary> ### [`v6.0.5`](https://redirect.github.com/vitejs/vite-plugin-react/blob/HEAD/packages/plugin-react/CHANGELOG.md#605-2026-07-30) [Compare Source](https://redirect.github.com/vitejs/vite-plugin-react/compare/f4b549822ec239799d746c030abb0b9a7d8f0a04...68c0cb8796ce18bd049c3d05c5210eaf0617eac0) ##### Fixed the react compiler preset filter to be linear ([#​1353](https://redirect.github.com/vitejs/vite-plugin-react/pull/1353)) The improved filter in v6.0.3 was non-linear and caused a performance regression ([#​1349](https://redirect.github.com/vitejs/vite-plugin-react/issues/1349)). The filter was changed to be linear to avoid that. ### [`v6.0.4`](https://redirect.github.com/vitejs/vite-plugin-react/blob/HEAD/packages/plugin-react/CHANGELOG.md#604-2026-07-22) [Compare Source](https://redirect.github.com/vitejs/vite-plugin-react/compare/640fd358a0e82393acfce4e92e19a6ac6e1641a7...f4b549822ec239799d746c030abb0b9a7d8f0a04) ##### Fixed `$RefreshSig$ is not defined` error when running `vite dev` with `NODE_ENV=production` When running `vite dev` with `NODE_ENV=production`, the app errored with `$RefreshSig$ is not defined`. This error is now fixed. </details> <details> <summary>dorny/paths-filter (dorny/paths-filter)</summary> ### [`v4.0.3`](https://redirect.github.com/dorny/paths-filter/blob/HEAD/CHANGELOG.md#v403) [Compare Source](https://redirect.github.com/dorny/paths-filter/compare/v4.0.2...v4.0.3) - [Document safe handling of file list outputs in workflows](https://redirect.github.com/dorny/paths-filter/pull/326) - [Escape multi-line filenames in list-files shell and csv output](https://redirect.github.com/advisories/GHSA-7hc6-8hq5-9q2m) - [Add 'some-with-excludes' predicate quantifier](https://redirect.github.com/dorny/paths-filter/pull/322) - [Add contents permission to PR example](https://redirect.github.com/dorny/paths-filter/pull/248) - [Scope base-ignored warning to API path](https://redirect.github.com/dorny/paths-filter/pull/319) - [Update outputs in readme to account for the 'every' predicate-quantifier](https://redirect.github.com/dorny/paths-filter/pull/247) </details> <details> <summary>isomorphic-git/isomorphic-git (isomorphic-git)</summary> ### [`v1.38.10`](https://redirect.github.com/isomorphic-git/isomorphic-git/releases/tag/v1.38.10) [Compare Source](https://redirect.github.com/isomorphic-git/isomorphic-git/compare/v1.38.9...v1.38.10) ##### Bug Fixes - **statusMatrix:** do not traverse symlinks in GitWalkerFs ([#​1215](https://redirect.github.com/isomorphic-git/isomorphic-git/issues/1215)) ([#​2382](https://redirect.github.com/isomorphic-git/isomorphic-git/issues/2382)) ([90ea101](https://redirect.github.com/isomorphic-git/isomorphic-git/commit/90ea101d329daa84b99cc0140a6275896ebbaf68)) ### [`v1.38.9`](https://redirect.github.com/isomorphic-git/isomorphic-git/releases/tag/v1.38.9) [Compare Source](https://redirect.github.com/isomorphic-git/isomorphic-git/compare/v1.38.8...v1.38.9) ##### Bug Fixes - Preserve binary files when writing conflicted working tree ([#​2380](https://redirect.github.com/isomorphic-git/isomorphic-git/issues/2380)) ([b41b1ab](https://redirect.github.com/isomorphic-git/isomorphic-git/commit/b41b1abc3df87326e639b49d0694915540d6dfb5)) ### [`v1.38.8`](https://redirect.github.com/isomorphic-git/isomorphic-git/releases/tag/v1.38.8) [Compare Source](https://redirect.github.com/isomorphic-git/isomorphic-git/compare/v1.38.7...v1.38.8) ##### Bug Fixes - unsafe symlink from cherry pick ([#​2377](https://redirect.github.com/isomorphic-git/isomorphic-git/issues/2377)) ([4664c8e](https://redirect.github.com/isomorphic-git/isomorphic-git/commit/4664c8e1147c3c7ba87c027e92093d28607ef4c0)) </details> <details> <summary>postcss/postcss (postcss)</summary> ### [`v8.5.26`](https://redirect.github.com/postcss/postcss/blob/HEAD/CHANGELOG.md#8526) [Compare Source](https://redirect.github.com/postcss/postcss/compare/8.5.25...8.5.26) - Fixed `list.split()` regression (by [@​lazerg](https://redirect.github.com/lazerg)). - Track symlinks in path protection in source map loading (by [@​drengir1](https://redirect.github.com/drengir1)). ### [`v8.5.25`](https://redirect.github.com/postcss/postcss/blob/HEAD/CHANGELOG.md#8525) [Compare Source](https://redirect.github.com/postcss/postcss/compare/8.5.24...8.5.25) - Fixed 8.5.17 visitor regression. - Fixed `list.split()` for non-string values (by [@​amir-rezaei](https://redirect.github.com/amir-rezaei)). ### [`v8.5.24`](https://redirect.github.com/postcss/postcss/blob/HEAD/CHANGELOG.md#8524) [Compare Source](https://redirect.github.com/postcss/postcss/compare/8.5.23...8.5.24) - Preserve the BOM after the processing (by [@​hdimer](https://redirect.github.com/hdimer)). ### [`v8.5.23`](https://redirect.github.com/postcss/postcss/blob/HEAD/CHANGELOG.md#8523) [Compare Source](https://redirect.github.com/postcss/postcss/compare/8.5.22...8.5.23) - Do not load source map without `opts.from` for security reasons. ### [`v8.5.22`](https://redirect.github.com/postcss/postcss/blob/HEAD/CHANGELOG.md#8522) [Compare Source](https://redirect.github.com/postcss/postcss/compare/8.5.21...8.5.22) - Fixed custom property losing semicolon before a comment (by [@​sarathfrancis90](https://redirect.github.com/sarathfrancis90)). ### [`v8.5.21`](https://redirect.github.com/postcss/postcss/blob/HEAD/CHANGELOG.md#8521) [Compare Source](https://redirect.github.com/postcss/postcss/compare/8.5.20...8.5.21) - Fixed childless at-rule losing semicolon before comment (by [@​sarathfrancis90](https://redirect.github.com/sarathfrancis90)). - Fixed docs (by [@​isker](https://redirect.github.com/isker)). ### [`v8.5.20`](https://redirect.github.com/postcss/postcss/blob/HEAD/CHANGELOG.md#8520) [Compare Source](https://redirect.github.com/postcss/postcss/compare/8.5.19...8.5.20) - Fixed missing space if `AtRule#params` is set after (by [@​sarathfrancis90](https://redirect.github.com/sarathfrancis90)). - Fixed mixing AST error on warnings (by [@​MahinAnowar](https://redirect.github.com/MahinAnowar)). </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/block/buzz). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yODAuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjEyLjAiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…ock#4439) This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [@tanstack/react-virtual](https://tanstack.com/virtual) ([source](https://redirect.github.com/TanStack/virtual/tree/HEAD/packages/react-virtual)) | [`3.14.8` → `3.14.9`](https://renovatebot.com/diffs/npm/@tanstack%2freact-virtual/3.14.8/3.14.9) |  |  | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/1) for more information. --- ### Release Notes <details> <summary>TanStack/virtual (@​tanstack/react-virtual)</summary> ### [`v3.14.9`](https://redirect.github.com/TanStack/virtual/blob/HEAD/packages/react-virtual/CHANGELOG.md#3149) [Compare Source](https://redirect.github.com/TanStack/virtual/compare/@tanstack/react-virtual@3.14.8...@tanstack/react-virtual@3.14.9) ##### Patch Changes - Updated dependencies \[[`a5417b4`](https://redirect.github.com/TanStack/virtual/commit/a5417b4b0d3c82876747bb9635db7239c28d3e44)]: - [@​tanstack/virtual-core](https://redirect.github.com/tanstack/virtual-core)@​3.17.7 </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/block/buzz). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4zLjIiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
## Summary - temporarily allow the informational `RUSTSEC-2026-0243` advisory for the retired `nostr-relay-pool` crate - document the exact MeshLLM → `nostr-sdk 0.44.1` transitive path and removal condition - keep every other advisory and the global dependency policy enforced ## Why an exception RustSec provides no patched `nostr-relay-pool` release because the standalone crate was absorbed into `nostr-sdk >= 0.45`. Buzz inherits it through pinned MeshLLM v0.74. A direct test bump to `nostr-sdk 0.45.1` removed the retired crate but produced 13 MeshLLM API compilation errors, so the durable fix requires an upstream source migration rather than a lockfile update. This narrow exception restores the required Security check while that migration is completed. It must be removed once MeshLLM adopts `nostr-sdk >= 0.45`. ## Validation - `bin/cargo-deny --locked check --config deny.toml advisories` - `bin/cargo-deny --locked check` - `git diff --check origin/main...HEAD` - mandatory pre-push Rust and desktop/Tauri checks ## Scope One four-line `deny.toml` addition. No Rust source, lockfile, runtime, or release behavior changes. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [@types/react](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react) ([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react)) | [`19.2.17` → `19.2.18`](https://renovatebot.com/diffs/npm/@types%2freact/19.2.17/19.2.18) |  |  | | [@types/react-dom](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react-dom) ([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom)) | [`19.2.3` → `19.2.4`](https://renovatebot.com/diffs/npm/@types%2freact-dom/19.2.3/19.2.4) |  |  | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/1) for more information. --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/block/buzz). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4xMi4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Wes <wesbillman@users.noreply.github.com>
## Why Selecting or typing a member whose display name extends another member's name, such as `@Fast Fizz Codex`, could emit p-tags for both identities and wake the wrong agent. ## What - Resolve overlapping member-name matches by choosing the longest valid display name at each mention offset - Preserve separately typed short-name mentions at different offsets - Add regression coverage for selected team expansions and manually typed prefix collisions ## Risk Assessment Low to medium — this changes Desktop mention routing only. Exact mentions and distinct offsets remain supported; same-length ambiguous display names remain conservatively tagged because text alone cannot disambiguate them. Will resolve block#2909 Generated with Goose Signed-off-by: Hardworking Honey <c5c455215c2506cb8ba776518cec804af62d3a0526e32d496a22072e395042b9@buzz.block.builderlab.xyz> Signed-off-by: Atish Patel <atish@squareup.com> Co-authored-by: Hardworking Honey <c5c455215c2506cb8ba776518cec804af62d3a0526e32d496a22072e395042b9@buzz.block.builderlab.xyz>
…hive + P4a aggregation/D6 (block#4000) ## What Implements Phases 2 and 4a of the Usage v2 plan (plan events `d0268cd0`/`0e95b035`), extending the archive backend to emit, transport, archive, and aggregate both cache categories and billing identity fail-closed. ### P2 — emission, transport, archive **Tri-state accumulators** (`Unseen`/`Exact`/`Unknown`) for cache-read and cache-write in `buzz-agent` turn and session state. Absent field = Unknown (never zero) through the full pipeline. No `unwrap_or(0)` on the cache path. Both cache folds are gated on usage-bearing responses (same gate as the total-state and identity folds) — a response with no usage at all must not poison either accumulator. **Overflow-aware input token parsing and accumulation** — closed end-to-end from parse through wire to ACP: - `sum_usage()` returns `SumUsageResult` (`Exact(u64)` | `Overflow`) — checked arithmetic, never clamps. `anthropic_input_tokens()` returns `Option<SumUsageResult>` since it sums three fields (`input_tokens + cache_read_input_tokens + cache_creation_input_tokens`) that can collectively overflow. Single-field callers (`prompt_tokens`, `completion_tokens`, etc.) convert via `.into_exact()` — their single-field sums cannot overflow. - `LlmResponse.input_tokens_overflowed: bool` propagates the parse-layer signal into the run loop. When set, `input_tokens` is `None` (clamped value discarded), the context-gate baseline (`last_request_input_tokens`) is frozen at its prior reading, and `turn_input_tokens` is poisoned to `TurnIOState::Poisoned` before any emission — including mid-turn `emit_usage_update` calls. A dedicated enum on `LlmResponse.input_tokens` would ripple into ~20 existing test assertions on `r.input_tokens == Some(...)`; the bool flag confines the change to the two call sites that check it. - `TurnIOState` (`Unseen`/`Exact`/`Poisoned`) for input and output: per-round fold uses `checked_add`; overflow poisons permanently at turn and session level, no healing. Absence does not poison (pass-2-cleared contract unchanged). Wire emission omits `accumulatedInputTokens`/`accumulatedOutputTokens` when poisoned — never null, never `u64::MAX`. ACP treats absent = publisher-poisoned: `delta_reliable: false`, null turn fields, null cumulative for that category; session cumulative stays unknown for all subsequent turns once poisoned. **Conditional wire emission** for `accumulatedCachedInputTokens` and new `accumulatedCacheWriteTokens`: fields are omitted when the cumulative is Unseen or Unknown. ACP `_goose/unstable/session/update` contract documented next to the payload with tests for all absence/zero variants. **`PricingIdentity` stamping (publisher-side)**: - `pricing_authority()`: canonical parsed-URL endpoint comparison against the official allowlist — HTTPS only, exact allowlisted host (lookalike-safe), default port (omitted or explicit :443), required API base path, rejects userinfo/query/fragment/path-prefix lookalikes. - Model: the actually-requested `request_model` after mesh/auto resolution (not `effective_model_str`). - Turn discipline: identity retained only while ALL usage in the current turn carries one identical proven identity; any mismatch, unproven-usage-bearing response, or unpaired cumulative snapshot poisons to absent; a later matching notification does not heal a mixed turn. **ACP `UsageTracker` identity fold**: per-in-flight-turn tri-state identity accumulator replacing last-update-wins. Any absent identity on a token-advancing notification or exact mismatch poisons to absent; poison survives later updates; reset in `begin_turn()`/`take()`; reset also when a request fails (baseline cleared so preflight gate cannot stay frozen sub-threshold on retries). **M3 migration**: adds `turn_cache_write_tokens`, `cumulative_cache_write_tokens`, `pricing_authority`, `pricing_model`, `pricing_cache_class` to `agent_metric_index`. Additive, idempotent, guarded per-column by marker. M2 migration also guarded per-column (turn and cumulative cache-read columns checked and added independently; marker commits only after both are present). Fresh-DB schema includes all columns. **First-turn baselines**: `seed_zero_baseline` seeds `last_input: Some(0)`, `last_output: Some(0)`, `last_cached_input: Some(0)`, `last_cache_write: Some(0)`, and `last_total: Some(0)` — all have the known-zero-at-spawn argument. Absent fields from incoming snapshots still produce unknown (tri-state unchanged). Sessions buzz-acp did not spawn (no seed) remain fail-closed on turn one. **`ReportedUsage` TS mirror**: `cacheReadTokens`, `cacheWriteTokens`, `freshInputTokens` added to `tauriArchive.ts` as `UsageField` members, field-for-field with the Rust struct. ### P4a — aggregation layer **Extended S-1 ladder** to cache-read and cache-write via the same `ladder_token` path as the existing token fields. **`freshInputTokens` derivation**: checked arithmetic, fail-closed — absent cache fields produce Unknown (not zero), overflow and `cacheRead+cacheWrite > input` both produce `incomplete: true`. Aggregated as a `UsageField`. **D6 comparator**: `sort_value()` = provider total when known, else `input+output` when both known, else `None` (unknown-last). Replaces the prior total-only comparator for both agent-level and model-level sort. Ships a pinned test vector that the TS render layer (P5) must match. ## Test coverage - `buzz-agent`: 440 lib + 15 integration (golden_transcripts) — includes 13 new `cache_total_state_tests`; 14 new `turn_io_state_tests`; 3 new `sum_usage_*` tests (exact single-field, exact two-field, overflow signals correctly); 3 new `parse_anthropic_*` tests (overflow flag set + value cleared, normal sum no flag, absent usage no flag); end-to-end golden transcript drives real subprocess with Anthropic-shaped `input_tokens: u64::MAX, cache_read: 1` response and asserts `accumulatedInputTokens` absent from the emitted `usage_update` — no logic duplication; 3 wire pin tests; 4 `fold_pricing_identity_*` tests; `pricing_authority()` explicit-:443 acceptance - `buzz-acp`: 700 tests (691 lib + 9 integration) — 4 new usage tests (absent input → unreliable+null; absent output → unreliable+null; goose-shaped both present unchanged; poison mid-session); 3 ACP behavior tests; 7 pool lifecycle tests - Desktop (Rust): 2259+ tests — 14 new P4a pinned tests; 2 M3 round-trip tests; 1 serde key-shape test; 2 M2 partial-schema migration tests; first-turn cache round-trip test ## Related PRs - P1 NIP-AM spec: [block#4632](block#4632) - P3 pricing table: [block#4629](block#4629) - UI (P5): [block#4001](block#4001) --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
## Summary - deliver legacy ACP standing context once per live session, committing delivery state only after a successful turn - send only new thread/DM event deltas on later turns, with fail-open behavior for missing IDs and failed/cancelled prompts - fence native steer delivery acknowledgements by ACP session identity so stale acks cannot poison replacement sessions - keep context hints truthful when a fetch contains only the triggering event versus history delivered earlier ## Validation The pre-push hook passed on exact pushed head `6a768f1bc80fe63c686acf8d730f177fff8add3c`: - `branch-skew` - `desktop-check` - `desktop-typecheck` - `desktop-test` - `rust-tests` - `desktop-tauri-checks` Focused regression tests were also run while iterating: - `channel_prompt_commits_delivery_state_only_after_acp_success` - `in_flight_stale_native_steer_ack_cannot_update_replacement_session` - thread/DM trigger-only versus previously-delivered context hint tests ## Known limitations and follow-ups A local Goose smoke timed out at `session/new`. This diff does not change code that executes at or before `session/new`; its earliest affected runtime behavior is delivery-state insertion after session creation succeeds. The smoke failure is therefore bounded as environmental or pre-existing, but no successful live-provider turn was obtained. Scripted ACP wire/lifecycle tests carry the regression coverage. - block#5421 — distinguish post-delta, already-delivered, and fetch-truncated context counts - block#5422 — define a standing-context re-delivery policy if a legacy provider compacts it away Durable process-restart/session resume remains out of scope for this slice of block#5342. block#5386 also remains separate pending upstream adapter support. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Merge exact upstream commit 563e434, which contains block#5423, while preserving the fork's trusted workflow tree unchanged. Signed-off-by: Reinhold <310554180+reinhold-ph@users.noreply.github.com>
Carry the Mac-accepted Thin-v6 product patch from exact source 3d273ec onto the upstream baseline containing block#5423. Preserve the trusted manual build workflow and omit the obsolete candidate workflow plus the later deliberately red test. Apply rustfmt 1.95 formatting and keep managed_agents/runtime.rs within the current 1000-line source-size ratchet. Signed-off-by: Reinhold <310554180+reinhold-ph@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Buzz v6 is intended to be a lean desktop client that discovers and verifies existing external AI runtimes instead of bundling large Codex/Claude installations. The accepted Thin-v6 candidate proved that model on macOS, but its ACP session path repeated large standing instructions and prior thread history on every short turn.
This milestone combines the previously accepted Thin-v6 product patch with exact upstream merge
563e4346da37d0fb2e9ec1c95e7f1eba79f83040(block/buzz#5423). That upstream change delivers legacy standing context once per live session and sends only new thread/DM event deltas on later turns.563e4346…into forkmain. This is a deliberate upstream product-baseline sync: 150 upstream commits and 987 changed paths precede the two local integration commits.3d273ec15ef7d4d3f8a33638603b0bc6800f5dbf.thin-v6-macos-arm64.ymlworkflow and later deliberately failing test commita30377a2…..github/workflowssubtree exactly; the existing manual exact-SHA test-release workflow remains the only build path.managed_agents/runtime.rswithin the 1000-line source-size ratchet.This PR does not dispatch a build, publish a release, install an app, merge itself, or add the still-open negotiated developer-instruction transport from
block/buzz#5386/agentclientprotocol/codex-acp#379.Related issue
block/buzz#5423.block/buzz#3242(repeated ACP context) andblock/buzz#5342(restart continuity; still separate and unresolved).Testing
Passed on the exact local head:
563e4346…is an ancestor.cargo fmt --manifest-path desktop/src-tauri/Cargo.toml --all -- --checkcargo fmt --manifest-path Cargo.toml --all -- --checknode desktop/scripts/check-file-sizes.mjsnode desktop/scripts/check-px-text.mjsnode desktop/scripts/check-pubkey-truncation.mjsgit diff --checkNot run on this combined head:
cargo test -p buzz-acpis blocked offline because the current upstream lockfile pins uncachedanyhow 1.0.104.buzz-terminalworkspace dependency requires uncachedportable-pty.node_modules; older worktree dependencies were intentionally not reused.