mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
tho/message-embed
683
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
538e5e113f |
chore(release): release Buzz Desktop version 0.5.9 (#5521)
## Buzz Desktop release v0.5.9 - **Frozen main:** `f8f2ef0440e7a074223ec04dc3b32d817b8b9d9b` - **Reviewed candidate:** `ee33722615ca1e7b8efb03e2ed641d99448c8899` - **Previous desktop release:** `desktop-v0.5.8` - **Proposed immutable tag:** `desktop-v0.5.9` This PR may be **squash merged** after the Desktop Release Candidate check and all protected-branch checks pass. Merging authorizes publication of the exact reviewed candidate; later or unrelated changes on `main` cannot alter it. The checked-in changelog accounts for every non-merge commit in the release range. The Desktop tag points to the reviewed candidate commit, not the later squash commit. Publication remains bound to that immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com> |
||
|
|
5e4c05f90b |
feat(desktop): NIP-AM agent-usage backend — P2 emission/transport/archive + P4a aggregation/D6 (#4000)
## What Implements Phases 2 and 4a of the Usage v2 plan (plan events `d0268cd0`/`0e95b035`), extending the archive backend to emit, transport, archive, and aggregate both cache categories and billing identity fail-closed. ### P2 — emission, transport, archive **Tri-state accumulators** (`Unseen`/`Exact`/`Unknown`) for cache-read and cache-write in `buzz-agent` turn and session state. Absent field = Unknown (never zero) through the full pipeline. No `unwrap_or(0)` on the cache path. Both cache folds are gated on usage-bearing responses (same gate as the total-state and identity folds) — a response with no usage at all must not poison either accumulator. **Overflow-aware input token parsing and accumulation** — closed end-to-end from parse through wire to ACP: - `sum_usage()` returns `SumUsageResult` (`Exact(u64)` | `Overflow`) — checked arithmetic, never clamps. `anthropic_input_tokens()` returns `Option<SumUsageResult>` since it sums three fields (`input_tokens + cache_read_input_tokens + cache_creation_input_tokens`) that can collectively overflow. Single-field callers (`prompt_tokens`, `completion_tokens`, etc.) convert via `.into_exact()` — their single-field sums cannot overflow. - `LlmResponse.input_tokens_overflowed: bool` propagates the parse-layer signal into the run loop. When set, `input_tokens` is `None` (clamped value discarded), the context-gate baseline (`last_request_input_tokens`) is frozen at its prior reading, and `turn_input_tokens` is poisoned to `TurnIOState::Poisoned` before any emission — including mid-turn `emit_usage_update` calls. A dedicated enum on `LlmResponse.input_tokens` would ripple into ~20 existing test assertions on `r.input_tokens == Some(...)`; the bool flag confines the change to the two call sites that check it. - `TurnIOState` (`Unseen`/`Exact`/`Poisoned`) for input and output: per-round fold uses `checked_add`; overflow poisons permanently at turn and session level, no healing. Absence does not poison (pass-2-cleared contract unchanged). Wire emission omits `accumulatedInputTokens`/`accumulatedOutputTokens` when poisoned — never null, never `u64::MAX`. ACP treats absent = publisher-poisoned: `delta_reliable: false`, null turn fields, null cumulative for that category; session cumulative stays unknown for all subsequent turns once poisoned. **Conditional wire emission** for `accumulatedCachedInputTokens` and new `accumulatedCacheWriteTokens`: fields are omitted when the cumulative is Unseen or Unknown. ACP `_goose/unstable/session/update` contract documented next to the payload with tests for all absence/zero variants. **`PricingIdentity` stamping (publisher-side)**: - `pricing_authority()`: canonical parsed-URL endpoint comparison against the official allowlist — HTTPS only, exact allowlisted host (lookalike-safe), default port (omitted or explicit :443), required API base path, rejects userinfo/query/fragment/path-prefix lookalikes. - Model: the actually-requested `request_model` after mesh/auto resolution (not `effective_model_str`). - Turn discipline: identity retained only while ALL usage in the current turn carries one identical proven identity; any mismatch, unproven-usage-bearing response, or unpaired cumulative snapshot poisons to absent; a later matching notification does not heal a mixed turn. **ACP `UsageTracker` identity fold**: per-in-flight-turn tri-state identity accumulator replacing last-update-wins. Any absent identity on a token-advancing notification or exact mismatch poisons to absent; poison survives later updates; reset in `begin_turn()`/`take()`; reset also when a request fails (baseline cleared so preflight gate cannot stay frozen sub-threshold on retries). **M3 migration**: adds `turn_cache_write_tokens`, `cumulative_cache_write_tokens`, `pricing_authority`, `pricing_model`, `pricing_cache_class` to `agent_metric_index`. Additive, idempotent, guarded per-column by marker. M2 migration also guarded per-column (turn and cumulative cache-read columns checked and added independently; marker commits only after both are present). Fresh-DB schema includes all columns. **First-turn baselines**: `seed_zero_baseline` seeds `last_input: Some(0)`, `last_output: Some(0)`, `last_cached_input: Some(0)`, `last_cache_write: Some(0)`, and `last_total: Some(0)` — all have the known-zero-at-spawn argument. Absent fields from incoming snapshots still produce unknown (tri-state unchanged). Sessions buzz-acp did not spawn (no seed) remain fail-closed on turn one. **`ReportedUsage` TS mirror**: `cacheReadTokens`, `cacheWriteTokens`, `freshInputTokens` added to `tauriArchive.ts` as `UsageField` members, field-for-field with the Rust struct. ### P4a — aggregation layer **Extended S-1 ladder** to cache-read and cache-write via the same `ladder_token` path as the existing token fields. **`freshInputTokens` derivation**: checked arithmetic, fail-closed — absent cache fields produce Unknown (not zero), overflow and `cacheRead+cacheWrite > input` both produce `incomplete: true`. Aggregated as a `UsageField`. **D6 comparator**: `sort_value()` = provider total when known, else `input+output` when both known, else `None` (unknown-last). Replaces the prior total-only comparator for both agent-level and model-level sort. Ships a pinned test vector that the TS render layer (P5) must match. ## Test coverage - `buzz-agent`: 440 lib + 15 integration (golden_transcripts) — includes 13 new `cache_total_state_tests`; 14 new `turn_io_state_tests`; 3 new `sum_usage_*` tests (exact single-field, exact two-field, overflow signals correctly); 3 new `parse_anthropic_*` tests (overflow flag set + value cleared, normal sum no flag, absent usage no flag); end-to-end golden transcript drives real subprocess with Anthropic-shaped `input_tokens: u64::MAX, cache_read: 1` response and asserts `accumulatedInputTokens` absent from the emitted `usage_update` — no logic duplication; 3 wire pin tests; 4 `fold_pricing_identity_*` tests; `pricing_authority()` explicit-:443 acceptance - `buzz-acp`: 700 tests (691 lib + 9 integration) — 4 new usage tests (absent input → unreliable+null; absent output → unreliable+null; goose-shaped both present unchanged; poison mid-session); 3 ACP behavior tests; 7 pool lifecycle tests - Desktop (Rust): 2259+ tests — 14 new P4a pinned tests; 2 M3 round-trip tests; 1 serde key-shape test; 2 M2 partial-schema migration tests; first-turn cache round-trip test ## Related PRs - P1 NIP-AM spec: [#4632](https://github.com/block/buzz/pull/4632) - P3 pricing table: [#4629](https://github.com/block/buzz/pull/4629) - UI (P5): [#4001](https://github.com/block/buzz/pull/4001) --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> |
||
|
|
e1ff91ecc1 |
chore(deps): update rust crate anyhow to v1.0.104 (#4447)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [anyhow](https://redirect.github.com/dtolnay/anyhow) | dependencies | patch | `1.0.103` → `1.0.104` | | [anyhow](https://redirect.github.com/dtolnay/anyhow) | workspace.dependencies | patch | `1.0.103` → `1.0.104` | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/1) for more information. --- ### Release Notes <details> <summary>dtolnay/anyhow (anyhow)</summary> ### [`v1.0.104`](https://redirect.github.com/dtolnay/anyhow/releases/tag/1.0.104) [Compare Source](https://redirect.github.com/dtolnay/anyhow/compare/1.0.103...1.0.104) - Update `syn` dev-dependency to version 3 </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/block/buzz). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4zLjIiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> |
||
|
|
c815a9c6e1 |
chore(release): release Buzz Desktop version 0.5.8 (#5326)
## Buzz Desktop release v0.5.8 - **Frozen main:** `6a17d035f79ad582ca3f4f3cdc38d376f2c4087f` - **Reviewed candidate:** `f3de860574bb3119018b4592353e9761635aeb07` - **Previous desktop release:** `desktop-v0.5.7` - **Proposed immutable tag:** `desktop-v0.5.8` This PR may be **squash merged** after the Desktop Release Candidate check and all protected-branch checks pass. Merging authorizes publication of the exact reviewed candidate; later or unrelated changes on `main` cannot alter it. The checked-in changelog accounts for every non-merge commit in the release range. The Desktop tag points to the reviewed candidate commit, not the later squash commit. Publication remains bound to that immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com> |
||
|
|
13c9e900c8 |
chore(release): release Buzz Desktop version 0.5.7 (#5252)
## 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> |
||
|
|
74b913cff8 |
fix(desktop): isolate relay admission tests (#5221)
## 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> |
||
|
|
dcc1231d6d |
fix(desktop): externalize boot <style> to prevent Tauri CSP nonce override (#5242)
## Problem Two v0.5.6-only regressions were introduced by #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 #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> |
||
|
|
3855687e76 |
chore(release): release Buzz Desktop version 0.5.6 (#5214)
## Buzz Desktop release v0.5.6 - **Frozen main:** `78c87ae20e182fffdd99744d6c9ff99df82b159c` - **Reviewed candidate:** `277d98a5cfb6d3b9af8b75122988f7a7df33ed5d` - **Previous desktop release:** `desktop-v0.5.5` - **Proposed immutable tag:** `desktop-v0.5.6` 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> |
||
|
|
1922d49cb2 |
feat(desktop): adding rich link previews to messages (#3818)
## Overview **Category:** improvement **User impact:** Link previews appear in the composer and travel as privacy-safe sender-authored snapshots, so recipients never contact the linked site merely by opening a conversation. **Problem:** Cold-cache link paste could freeze the composer before the URL painted; recipient-side unfurling leaked visits; invalid or unresolved preview work could interfere with sending or leave dead cards behind. **Solution:** Paint pasted links before starting cold resolver work, resolve only in the sender's composer, attach only complete validated snapshots at Send, and render authored snapshots without recipient fallback fetching. ## Behavior - **Cold paste stays responsive:** bare and angle-bracket URL paste paths commit the visible link before resolver work begins. - **Sender-only fetching:** metadata is resolved while composing; recipients render only the sender-authored snapshot. - **Send never waits:** pending, failed, invalid, and unsendable previews are omitted. They do not block or cancel the message. - **Terminal misses disappear:** failed, timed-out, or 404 resolver results remove the composer card while preserving visible link text. - **Display-text links work:** Markdown links such as `[review the pull request](…)` produce and send the same snapshots as bare URLs. - **Compact and Rich presentation:** Compact remains the default; Rich preserves source description line breaks and paragraphs. - **Immediate draft-wide dismissal:** clicking × immediately hides all previews for the draft, suppresses links pasted later, and emits only `["link-preview", "none"]`. No confirmation detour. Suppression resets after send or clearing the draft. - **Zero recipient fallback:** missing, stale, malformed, off-relay, unsupported, or suppressed snapshots remain ordinary visible links; recipients never regenerate them. ## Implementation - Resolve previews from deferred composer URL state so paste can paint first. - Upload finished preview media to the active community relay and snapshot only valid, sendable media references. - Atomically capture ready snapshots at submit time; never append a late preview after send. - Validate snapshot and suppression tags in desktop/native and relay ingestion, rejecting duplicate or mixed forms. - Render composer previews as stable 55px attachment cards at desktop and narrow widths. - Add deterministic E2E coverage for cold paste, ready/pending/failed/invalid previews, display-text links, multiline Rich descriptions, immediate dismissal, later-pasted links, and suppression reset. ## Validation Validated head: `9807ba8952f190e76153834abf8ab61dd40be5e2` - Push hooks passed: `check-push-org`, branch skew, desktop check, mobile tests, desktop tests, Rust tests, and desktop Tauri checks. - Focused screenshot E2E at the validated head: 5/5 passed across Compact/Rich composer and recipient states, 800px/420px geometry, display-text links, multiline descriptions, and immediate dismissal. - PR CI was triggered for this exact head and is currently running; completed checks are green at the time of this update. - Worktree is clean and both PR head and validated branch resolve to `9807ba895…`. ## Screenshots ### Compact composer | Loading | Ready | |---|---| |  |  | ### Rich composer | Loading | Ready | |---|---| |  |  | ### Responsive composer | 800px loading | 800px ready | |---|---| |  |  | | 420px loading | 420px ready | |---|---| |  |  | ### Recipient presentation | Compact | Rich | |---|---| |  |  | ### Display-text Markdown link | Composer | Recipient | |---|---| |  |  | ### Rich multiline description  ### Immediate dismissal | Before × | Immediately after × | |---|---| |  |  | --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
742e8d1197 |
fix(buzz-agent): Responses reasoning summary, Anthropic display:summarized, ACP v2 messageId (#5195)
Three pre-existing gaps in the buzz-agent observer feed fixed together
per Will's ruling ("all 3 in the current PR"):
1. **OpenAI/DBv2-GPT route** — `responses_body` never requested
`reasoning.summary`; GPT-family models billed thinking tokens but
returned `summary: []`.
2. **Anthropic/DBv2-Claude route** — `anthropic_thinking_config()` never
sent `thinking.display`; newest Claude models (Opus 5, Sonnet 5, Fable
5, Mythos 5, Opus 4.7/4.8, Mythos Preview) default to
`display:"omitted"`, returning thinking blocks with an empty `thinking`
field — observer rendered nothing.
3. **ACP v2 compliance** — buzz-agent negotiates ACP v2 but emitted
`agent_thought_chunk` and `agent_message_chunk` without `messageId`,
which ACP v2's `ContentChunk` requires (`messageId` + `content` both
required at schema head `d13d1baa`).
## Changes
**`crates/buzz-agent/src/config.rs`**
- New `ThinkingSummary` enum (`Auto`/`Concise`/`Detailed`) with
`BUZZ_AGENT_THINKING_SUMMARY` env var (default `Auto`); mirrors
`BUZZ_AGENT_THINKING_EFFORT` pattern
- `anthropic_thinking_config()` now emits `"display": "summarized"` in
both the adaptive shape and the manual-budget shape whenever thinking is
enabled
- Rewrote `is_adaptive_thinking_model` and `anthropic_thinking_config`
doc comments to match Anthropic's exact three-way per-model terminology
(doc:
https://platform.claude.com/docs/en/build-with-claude/thinking-troubleshooting#supported-models):
- Opus 4.6/4.7/4.8, Sonnet 4.6: **Off** — thinking OFF by default;
`type:"adaptive"` required to enable
- Opus 5, Sonnet 5: **On** — thinking on by default, can be disabled; we
still send `type:"adaptive"` to activate `output_config.effort`
- Fable 5, Mythos 5, Mythos Preview: **Always on** — thinking cannot be
disabled; we still send `type:"adaptive"` to activate
`output_config.effort`
**`crates/buzz-agent/src/llm.rs`**
- `responses_body` emits `reasoning.summary` alongside
`reasoning.effort` when effort is set (gated — no bare
`reasoning:{summary}` without effort)
- Covers both the pure-OpenAI Responses path and the DBv2 GPT-family
Responses path
**`crates/buzz-agent/src/agent.rs`**
- `agent_thought_chunk` carries `"messageId":
format!("{run_id}-thought-{round}")`
- `agent_message_chunk` carries `"messageId":
format!("{run_id}-message-{round}")`
- The two IDs are distinct (thought and assistant are two logical
messages per the ACP v2 Message ID RFD)
- `run_id` is a fresh random token per `session/prompt` invocation so
IDs are session-unique across multiple prompts
**`crates/buzz-agent/src/lib.rs`**
- `run_id` plumbed into `RunCtx` (was already generated in `run_prompt`,
just not threaded through)
**`crates/buzz-agent/tests/golden_transcripts.rs`**
- `test_acp_v2_chunks_carry_message_id` — negotiates v2, drives two
consecutive `session/prompt` calls, asserts: both chunk types carry
non-empty `messageId`; thought and message IDs are **distinct**; IDs do
**not** recur across the two prompts in the same ACP session
**`desktop/src-tauri/src/managed_agents/env_vars.rs`**
- `BUZZ_AGENT_THINKING_SUMMARY` added to `is_safe_to_reveal` allowlist
**`desktop/src-tauri/src/commands/agent_config_tests.rs`**
- Tests for `BUZZ_AGENT_THINKING_SUMMARY` allowlist entry
(case-insensitive)
## Tests added
- `parse_thinking_summary_round_trips_all_values`
- `parse_thinking_summary_unset_and_empty_yield_auto`
- `parse_thinking_summary_is_case_insensitive`
- `parse_thinking_summary_rejects_unknown_value`
- `thinking_summary_as_str_mapping`
- `responses_body_summary_present_iff_effort_set`
- `responses_body_emits_configured_summary_mode`
- `responses_body_concise_summary_mode`
- `anthropic_thinking_config_adaptive_emits_display_summarized`
- `anthropic_thinking_config_manual_budget_emits_display_summarized`
- `test_acp_v2_chunks_carry_message_id` (integration test — two-prompt
cross-session case)
## Notes
- **DBv2 gateway parity for `display`**: unverified — the DBv2 Claude
route proxies Anthropic Messages shape, but whether the gateway passes
`thinking.display` through is not confirmed. Flagged here rather than
blocking on it.
- buzz-acp and Desktop TS are unchanged — they already parse `messageId`
as optional and will pick it up from the wire automatically.
- Chat Completions and OpenRouter paths: untouched.
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
|
||
|
|
cc9a2f7833 |
fix(desktop): make terminal output selectable (#4980)
## Summary - mirror the retained canvas terminal grid into a transparent, selectable text layer - preserve the canvas renderer and terminal focus behavior for ordinary clicks - reconstruct wide and combining glyphs correctly for clipboard text ## Why Buzz Term renders output entirely on a canvas and deliberately called `preventDefault()` on viewport mouse-down, so native selection and copy could not work. A canvas has no selectable text even if that cancellation is removed. The transparent text layer stays aligned with the visible cell grid, lets WebView native selection drive drag highlighting and copy, and follows active-session switches without changing the renderer or PTY protocol. ## Validation - `pnpm --dir desktop typecheck` - `pnpm --dir desktop test` — 4,373 passed - pre-push `desktop-check`, `desktop-test`, and `branch-skew` hooks passed on `1f2a3f8db63f6fe36b4a28bc911aea3c5186b2b0` --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
60ae74b656 |
fix(desktop): use WEBKIT_DMABUF_RENDERER_FORCE_SHM for NVIDIA/AppImage (#3654) (#4505)
## Summary - Heuristic and `--safe-rendering` now set `WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` instead of `WEBKIT_DISABLE_DMABUF_RENDERER=1` - Legacy `DISABLE_DMABUF` stays owned so operators can still set `=0`/`=1` and take over the decision - Linux troubleshooting docs updated to match (#3654) ## Test plan - [ ] unit tests in `webkit_rendering::tests` - [ ] On NVIDIA + WebKitGTK 2.52: workspace switch no longer SIGSEGVs where the distro NVIDIA guard does not fire (Debian/Ubuntu proprietary-NVIDIA may still crash — #3654 stays open for that path) - [ ] `WEBKIT_DISABLE_DMABUF_RENDERER=0` still stands the heuristic down --------- Signed-off-by: Taksh <takshkothari09@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Alia <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@buzz.block.builderlab.xyz> Co-authored-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
769ac70b74 |
fix(media): require authenticated reads (#4610)
This change requires a valid signed Blossom authorization request and current relay membership for every media GET and HEAD request. It removes the unauthenticated compatibility path and updates desktop reads to send the required authorization. This blocks anonymous retrieval and access after relay-membership revocation. It does not yet bind a blob to its originating channel, so someone removed from a private channel can still read a known blob while remaining a relay member. That channel-ACL follow-up remains required before closing the full finding. ## Testing - `git diff --check origin/main...codex/security-media-read-auth` - Rebased onto `origin/main` at `5c98932` - Full CI pending Originating Buzz thread: `buzz://message?channel=3928fe05-df61-4b5d-b9c7-d623b9b10ea1&id=3c6c02312f763fbe0d2bfc33a6c1a362f91d0354f3d18b039cf7a0558c1439d1` --------- Signed-off-by: Jordan Mecom <jm@squareup.com> Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com> Signed-off-by: Eli Foster <efoster@squareup.com> Co-authored-by: Eli Foster <efoster@squareup.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6eb65919f1 |
feat(identity): recover desktop identity from a signed-in phone (#4845)
**Category:** new-feature **User Impact:** People who lose a desktop identity can securely restore it from a signed-in Buzz phone without creating a replacement identity. **Problem:** A fresh or identity-lost desktop could not recover its existing full Buzz identity from an already-authorized phone. **Solution:** Add a SAS-confirmed reverse NIP-AB transfer, durable desktop import, a dedicated mobile recovery entry point, and clearer desktop recovery dialogs with tested loading, drag-and-drop, and failure states. https://github.com/user-attachments/assets/e9215c9c-80d0-462f-9161-0fa184ca2f74 <details> <summary>File changes</summary> **crates/buzz-core/src/pairing/session.rs** Adds the reverse encrypted payload and source-completion state transitions used for phone-to-desktop recovery. **desktop/src-tauri/src/commands/identity.rs** Exposes the existing guarded identity commit path for recovery imports. **desktop/src-tauri/src/commands/pairing.rs** Adds recovery-mode pairing, durable nsec import, start serialization, stale-task protection, and explicit rejection of unsupported recovery payloads. **desktop/src-tauri/src/lib.rs** Registers the recovery pairing command. **desktop/src/app/App.tsx** Refreshes the recovered identity before continuing onboarding. **desktop/src/features/onboarding/machineOnboarding.ts** Adds recovery transitions to the onboarding state machine. **desktop/src/features/onboarding/ui/BackupPasswordTimeline.tsx** Adds the visual backup-to-password-to-unlock progression. **desktop/src/features/onboarding/ui/IdentityRecoveryPairing.tsx** Implements QR generation, copy fallback, SAS confirmation, cancellation, expiry, and completion UI. **desktop/src/features/onboarding/ui/MachineOnboardingFlow.tsx** Connects private-key, phone, and backup recovery paths to the onboarding flow. **desktop/src/features/onboarding/ui/NostrKeyImportForm.tsx** Polishes recovery dialogs, backup drag-and-drop, loading stability, and security copy. **desktop/src/shared/api/tauri.ts** Keeps the existing pairing API surface focused on standard desktop-to-mobile pairing. **desktop/src/shared/api/tauriPairing.ts** Adds the recovery pairing invoke without growing the ratcheted shared API file. **desktop/src/testing/e2eBridge.ts** Mocks recovery pairing commands and lifecycle events for browser tests. **desktop/tests/e2e/identity-lost.spec.ts** Covers lost-identity entry, QR/copy recovery, SAS, cancellation, expiry, success, errors, backup import, drag-and-drop, and screenshots. **desktop/tests/e2e/onboarding.spec.ts** Verifies recovered identities continue through harness setup without replacement-key side effects. **mobile/lib/features/pairing/pairing_page.dart** Adds recovery-only scanning and explicit identity-handoff warnings. **mobile/lib/features/pairing/pairing_provider.dart** Recognizes recovery codes, returns the signed-in nsec after mutual SAS approval, and waits for desktop completion. **mobile/lib/features/settings/settings_page.dart** Accepts the recovery route builder at the app composition boundary to preserve feature isolation. **mobile/lib/features/settings/settings_page/connection_section.dart** Adds the signed-in “Send identity to desktop” settings action. **mobile/test/features/pairing/pairing_page_test.dart** Covers recovery-only validation and handoff messaging. **mobile/test/features/pairing/pairing_provider_test.dart** Covers reverse payload encryption, confirmation ordering, success, failure, timeout, and cleanup. </details> ## Reproduction steps 1. Launch Buzz Desktop with identity-lost state and choose **Recover from your phone**. 2. Confirm the QR and persistent **Copy pairing code** fallback appear without layout shift. 3. On a signed-in phone, open **Settings → Send identity to desktop**, scan or paste the recovery code, and compare the six-digit SAS on both devices. 4. Confirm on both sides and verify Desktop restores the identity and continues to harness setup. 5. Repeat from identity-lost state with **Recover from a backup file**; verify picker and drag-and-drop both advance to password entry and restore the encrypted backup. 6. Exercise cancellation, mismatched/unsupported codes, expired sessions, and an invalid backup; verify each returns actionable, non-stuck UI. ## Screenshots ### Desktop phone recovery — complete flow | Recovery entry | Pairing QR | Code match | Receiving identity | |---|---|---|---| |  |  |  |  | ### iOS Simulator — complete handoff flow | Settings entry | Recovery scanner | Manual recovery code | Code confirmation | |---|---|---|---| |  |  |  |  | ### Encrypted backup recovery — adjusted file flow | File picker | Drag-and-drop target | Password step | |---|---|---| |  |  |  | ## Verification - `cargo test -p buzz-core pairing` — 71 passed - `just mobile-test` — 1,169 passed - `pnpm build:e2e && pnpm exec playwright test identity-lost.spec.ts --project=smoke` — 15 passed - Full pre-push gates — desktop checks, desktop unit tests, Rust tests, Tauri checks, and mobile tests passed --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> |
||
|
|
96ae141763 |
fix(desktop): skip native notifications outside app bundles (#5004)
## Summary - require the macOS process to be running from an actual `.app` bundle before initializing `UNUserNotificationCenter` - keep the existing bundle-identifier requirement - cover packaged, case-insensitive `.app`, raw `target/debug`, and extensionless paths ## Why PR #4799 guarded native notification initialization with `NSBundle.mainBundle.bundleIdentifier != nil`. Tauri embeds a bundle identifier in raw development executables, so `tauri dev` passed that guard and `UNUserNotificationCenter.current()` raised an uncaught `NSInternalInconsistencyException` because LaunchServices had no bundle proxy. ## Validation - focused macOS notification tests: 6 passed - direct raw debug executable no longer raises the notification-center exception - pre-commit formatting hook passed - pre-push package checks passed on pushed commit `f29a6664d2a863e7b8aa527f6149fd00b183e4de` The first push attempt hit an unrelated timing-test failure in `relay_admission::tests::concurrent_429_extends_the_window_for_parked_waiters`; its focused rerun passed, and the complete pre-push package suite passed on the next push. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
16cc3de6d6 |
fix(desktop): enforce owner-only access in internal builds (#4053)
## Problem Managed agents in internal Buzz builds should answer only their owner. Previously, an agent could keep a broader access setting and respond to other people, which did not match the access policy for internal builds. This PR makes owner-only access effective for every managed agent in internal builds and makes that restriction clear in the Desktop UI. Open source builds remain configurable. ## Changes - Enforce owner-only access when any managed agent starts or is deployed from an internal build. - Show the agent access control as locked to **Only me** in Desktop, with an explanation of why it cannot be changed. - Keep Welcome teammates working under the same rule without triggering unnecessary restarts. - Leave open source build behavior unchanged. This changes effective runtime access without rewriting stored or relay-advertised settings. The companion [#4064](https://github.com/block/buzz/pull/4064) explains the restriction in-thread when someone without access mentions an agent. The enforcement will remain inactive in shipped builds until [squareup/buzz-releases#74](https://github.com/squareup/buzz-releases/pull/74) marks internal releases during the build. ## Screenshots | Before | After | | --- | --- | |  |  | ## Tests Added coverage for: - Runtime enforcement for [locally run agents](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/managed_agents/runtime/tests.rs#L196) and [deployed agents](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/commands/agents_tests.rs#L510). - The [current-build deployment path](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/commands/agents_tests.rs#L455), [invalid stored access](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/managed_agents/access_policy.rs#L98), and the [local startup guard](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/managed_agents/env_vars/tests.rs#L149). - Consistent enforcement across [both agent backends](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/managed_agents/access_policy.rs#L112). - Welcome teammates created as [locally run](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src/features/onboarding/welcomeGuide.test.mjs#L384) or [deployed](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src/features/onboarding/welcomeGuide.test.mjs#L393) agents, including [access-only](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src/features/onboarding/welcomeKickoff.test.mjs#L202) and [runtime-related](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src/features/onboarding/welcomeKickoff.test.mjs#L225) restart behavior. The full Desktop Rust and JavaScript suites, type checks, formatting, clippy, and file-size checks passed. Playwright E2E was not run. --- Originated from Buzz channel [buzz-agent-control](buzz://channel?id=cf5dada7-e26a-4887-ae41-b3bd5f42d3b2). Supersedes #2537. --------- Signed-off-by: Tom Brow <tomb@block.xyz> Signed-off-by: npub1tquskdu6yc4h8l7xxtceculxw600grekeq0xg2ukqfrwl7vrzg3quz3gmp <58390b379a262b73ffc632f19c73e6769ef40f36c81e642b960246eff9831222@buzz.block.builderlab.xyz> Co-authored-by: npub1tquskdu6yc4h8l7xxtceculxw600grekeq0xg2ukqfrwl7vrzg3quz3gmp <58390b379a262b73ffc632f19c73e6769ef40f36c81e642b960246eff9831222@buzz.block.builderlab.xyz> Co-authored-by: Amp <amp@ampcode.com> |
||
|
|
a7ea86cdcf |
fix(desktop): enable the content security policy (#4614)
This change enables a Tauri content security policy that limits executable content to the packaged application and does not allow inline scripts. Relay, media, asset, and Tauri IPC schemes remain available for desktop compatibility. The policy contains the impact of a future renderer injection; it does not itself remove an injection bug. ## Testing - `git diff --check origin/main...codex/security-desktop-csp` - Rebased onto `origin/main` at `5c98932` - Full CI pending Originating Buzz thread: `buzz://message?channel=3928fe05-df61-4b5d-b9c7-d623b9b10ea1&id=3c6c02312f763fbe0d2bfc33a6c1a362f91d0354f3d18b039cf7a0558c1439d1` --------- Signed-off-by: Jordan Mecom <jm@squareup.com> Signed-off-by: Eli Foster <efoster@squareup.com> Co-authored-by: Eli Foster <efoster@squareup.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
719f9730d4 |
feat(desktop): allow leaving your final community (#3621)
**Category:** improvement **User Impact:** People can leave their final Buzz community and return to **Join or create a community** without losing their signed-in identity. **Problem:** Buzz Desktop blocked people from leaving when only one community remained. Its existing remove action also changed local configuration without ending relay membership. **Solution:** Allow the final community to be left. Buzz now asks the relay to end membership, removes the community locally only after acceptance, and returns the person to the community selector while keeping their identity signed in. If other communities remain, Buzz switches to one of them. Relay rejection or timeout keeps the community in place and shows an actionable retry error. <details> <summary>File changes</summary> **desktop/src/features/communities/leaveCommunity.ts** Adds signed kind 28936 publishing for active and inactive community relays with actionable timeout handling. **desktop/src/features/communities/leaveCommunity.test.mjs** Covers event shape, relay selection, acceptance gating, rejection, timeout messaging, and cleanup. **desktop/src/features/communities/useCommunities.tsx** Allows final-community removal and clears community-specific storage without touching identity. **desktop/src/features/communities/resolveCommunityRemoval.test.mjs** Covers final, active, and inactive community removal state transitions. **desktop/src/app/useCommunityNavigationTransitions.ts** Gates local removal on relay acceptance and routes to a fallback community or setup selector. **desktop/src/app/AppShell.tsx** Passes the asynchronous leave operation through shell entry points. **desktop/src/features/communities/ui/EditCommunityDialog.tsx** Replaces the local-only remove action with a pending-aware Leave Community action that retains actionable errors. **desktop/src/features/communities/ui/CommunitySwitcher.tsx** Enables leaving the final community and carries the asynchronous callback. **desktop/src/features/sidebar/ui/AppSidebar.tsx** Carries the asynchronous leave callback through sidebar props. **desktop/src/features/sidebar/ui/CommunityRail.tsx** Enables leaving the final community from rail settings. **desktop/src/features/sidebar/ui/SidebarProfileCard.tsx** Carries the asynchronous leave callback through profile community settings. **desktop/src/testing/e2eBridge.ts** Teaches the mock relay to accept NIP-43 leave events. **desktop/tests/e2e/community-rail.spec.ts** Updates leave interactions and verifies final-community setup navigation, storage cleanup, and identity preservation. </details> ### Reproduction steps 1. Run Buzz Desktop with a signed-in identity and one joined community. 2. Open Community settings and choose **Leave Community**. 3. Confirm the app shows **Join or create a community** and the existing identity remains signed in. 4. Repeat with two communities and confirm leaving the active one switches cleanly to the remaining community. 5. Reject or withhold the relay `OK` response and confirm the community remains configured with an actionable error in the dialog. ### Test plan - `pnpm check` - `pnpm build` - `pnpm test` (3,913 passing) - `pnpm build:e2e && pnpm exec playwright test tests/e2e/community-rail.spec.ts --grep "final community"` <img width="557" height="316" alt="image" src="https://github.com/user-attachments/assets/b628182f-cba5-451d-ae4b-bee8d8dd19aa" /> --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: npub14ndfusear8wdpe4kss8h7juc7wjk78atnqzf63zvppcpneknv4sq6x9370 <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz> Co-authored-by: npub14ndfusear8wdpe4kss8h7juc7wjk78atnqzf63zvppcpneknv4sq6x9370 <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> |
||
|
|
7334ad1e16 |
fix(desktop): route macos notification clicks (#4799)
## Summary - deliver macOS notifications through `UNUserNotificationCenter` - route notification clicks to the referenced channel or thread through the existing frontend activation path - preserve click targets across cold startup and frontend remounts with a small process-wide activation queue - keep Linux notification activation behavior unchanged ## Architecture A single `UNUserNotificationCenterDelegate` is installed during Tauri setup. Each notification stores its navigation target in `userInfo`. On click, Rust queues the target before emitting a wake-up event; the frontend atomically drains the queue and dispatches the existing notification action. The queue is the source of truth, which prevents cold-start loss and duplicate delivery. ## Validation Verified at `a81241611d617becf7640bee6fe56b5cdb4d0fab`: - Biome format/check and lint - TypeScript typecheck - repository and desktop-Tauri `cargo fmt --check` - repository and desktop-Tauri Clippy with `-D warnings` - full pre-push desktop tests and Tauri workspace checks/tests - desktop production build Manual macOS validation passed: after explicitly ad-hoc signing the local bundle with `xyz.block.buzz.app`, the operator confirmed real Notification Center delivery and click navigation. <details> <summary>Local macOS test procedure</summary> Tauri's generated ad-hoc signing identifier is not accepted by `UNUserNotificationCenter`. Re-sign the local bundle with its bundle identifier and keep other Buzz copies closed: ```bash just desktop-release-build APP="$HOME/.cache/cargo-target/aarch64-apple-darwin/release/bundle/macos/Buzz.app" codesign --force --deep --sign - \ --identifier xyz.block.buzz.app \ --entitlements desktop/src-tauri/Entitlements.plist \ "$APP" codesign --verify --deep --strict --verbose=2 "$APP" pkill -x buzz-desktop || true open -n "$APP" ``` </details> Buzz channel: `55e2bfca-1b38-48fb-9dc2-584d400501f3` --------- Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Signed-off-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> |
||
|
|
6c40ce394f |
feat(desktop): cap OpenClaw agent parallelism at 5 (#4019)
OpenClaw connects to a single shared Gateway daemon. Spawning the
default 10 ACP workers per agent is both resource-expensive and
architecturally wrong — each worker opens a separate gateway connection.
Tyler's ruling: cap at 5, lower if needed.
## Contract
Store the requested value (1–32) verbatim at every persistence and wire
boundary. Apply `effective = min(requested, harness_cap)` only at the
four enforcement points:
| Boundary | Implementation |
|---|---|
| Local spawn | `BUZZ_ACP_AGENTS` env var in child `Command` |
| Remote deploy | `launch.policy_env["BUZZ_ACP_AGENTS"]` + legacy
`parallelism` field |
| Restart badge | `SpawnConfigSnapshot.parallelism` stores effective
value; the diff surface displays what actually runs |
| UI copy | Amber hint when requested > cap; no `max` attribute, no
save-path clamp |
`BUZZ_ACP_AGENTS` is added to `RESERVED_ENV_KEYS` — the Desktop resolves
the effective value into `policy_env`; a user-supplied override in `env`
would bypass the cap and is silently stripped.
## Changes
**`managed_agents/parallelism.rs`** (new) — policy core:
- `OPENCLAW_MAX_PARALLELISM = 5`
- `harness_max_parallelism(command)` — keyed on
`normalize_command_identity` so path prefixes, `.exe` suffixes, and
other cosmetic differences are ignored
- `effective_parallelism(command, value)` — identity for uncapped
harnesses
- `acp_agents_value(command, parallelism)` — `env("BUZZ_ACP_AGENTS", …)`
helper
**`runtime.rs`** — spawn clamp: `BUZZ_ACP_AGENTS =
acp_agents_value(effective_command, record.parallelism)`
**`agents_deploy.rs`** — deploy egress clamp: `build_deploy_payload`
resolves `effective_parallelism` once from `descriptor.command`; both
`launch.policy_env["BUZZ_ACP_AGENTS"]` and the legacy top-level
`parallelism` field use that value — the two are always consistent
regardless of stale `record.agent_command` pins
**`spawn_snapshot.rs`** — `from_inputs` stores
`effective_parallelism(&descriptor.command, record.parallelism)` in the
`parallelism` field. Over-cap edits that don't change the pool (e.g. 10
→ 8, both clamp to 5 on OpenClaw) produce equal snapshots; cap crossings
(8 → 3) produce different snapshots.
**`AcpRuntimeCatalogEntry.max_parallelism: Option<u32>`** — derived from
the static definition command, not the probed `entry.command` (which may
be `null` for unavailable entries), so unavailable OpenClaw entries
still carry the cap. Propagated through all four catalog constructors
(builtin discovery, preset catalog construction, custom discovery,
custom-save response), IPC types
(`RawAcpRuntimeCatalogEntry.max_parallelism`), and the frontend catalog
type.
**UI** — `EditAgentAdvancedFields` and `PersonaAdvancedFields` show an
amber hint when `selectedRuntime.maxParallelism` is set and the current
value exceeds it. Cap and label come from the catalog entry — no
hardcoded 5 in TS. No `max` attribute on inputs; the input stays
`type="text"` with 1–32 copy.
**Docs** — `docs/remote-agents.md`: `BUZZ_ACP_AGENTS` moved from the
deliberately-non-reserved section to reserved; new contract documented.
`desktop/src/features/agents/AGENTS.md`: command-keyed execution policy
documented as the sanctioned second metadata source feeding the catalog
projection.
## Tests
**Rust** (`parallelism.rs`):
- `policy_table` — `harness_max_parallelism` and `effective_parallelism`
across all openclaw variants and uncapped harnesses
- `acp_agents_value_openclaw_above_cap_is_capped` — spawn-env seam
- `override_direction_*` — both override directions (openclaw runtime +
goose override; goose runtime + openclaw override)
- `summary_persona_inherited_*` — live persona wins over stale
`agent_command`
- `snapshot_export_carries_requested_definition_parallelism` — requested
value travels wire/sync unchanged
**Rust** (`spawn_snapshot/tests.rs`):
- `openclaw_above_cap_parallelism_snapshots_equal` — stored 10 vs 8,
both clamp to 5 → snapshots equal
- `openclaw_cap_crossing_parallelism_snapshots_differ` — 8 (clamps to 5)
vs 3 → snapshots differ
**Rust** (`discovery/presets.rs`):
- `openclaw_preset_unavailable_carries_max_parallelism` /
`openclaw_preset_available_carries_max_parallelism` — catalog metadata
present with `command: null` and with a resolved path
**Rust** (`agents_deploy.rs`):
- `launch_block_openclaw_over_cap_policy_env_is_capped` — direct
`launch.policy_env` seam
-
`deploy_payload_json_stale_goose_record_live_openclaw_descriptor_both_capped`
— stale `record.agent_command=goose`, live descriptor=openclaw: both
fields cap to 5
-
`deploy_payload_json_stale_openclaw_record_live_goose_descriptor_both_uncapped`
— stale `record.agent_command=openclaw`, live descriptor=goose: both
fields pass through requested
- `deploy_payload_json_explicit_openclaw_override_both_capped` —
explicit `agent_command_override=openclaw`: both fields cap to 5
**Rust** (`persona_events/stale_pin_tests.rs`):
- `apply_persona_snapshot_goose_to_custom_harness_drops_stale_goose_pin`
— custom-direction stale-pin drop (builtin pin → loaded custom harness
via `update_loaded_harness_registry`)
**TypeScript** (`agentParallelism.test.mjs`):
- `parallelismCapHint` — at/below cap (null), above cap (hint includes
label and cap value), singular form for cap=1, uncapped harness (null)
**TypeScript** (`tauri.test.mjs`):
- `fromRawAcpRuntimeCatalogEntry` round-trips `max_parallelism` →
`maxParallelism`; absent when `undefined`
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
|
||
|
|
6dbc946512 |
fix(desktop): make missing-command error actionable for released builds (#4802)
User-facing error for missing ACP harness commands has been pointing released-build users to run `cargo build --release --workspace` and read TESTING.md — both dead ends for anyone not building from source. Updated message acknowledges that antivirus software can quarantine bundled binaries and provides practical remediation steps. Preserves pointer to TESTING.md for source builds. Fixes issue context from #4491. Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub16v54tttfqacx9ycvc3k0ut0npj564ahcuajzy6qjvh57ntmsf4uq4806j2 <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@buzz.block.builderlab.xyz> |
||
|
|
8342dfcc58 |
chore(release): release Buzz Desktop version 0.5.5 (#4809)
## Buzz Desktop release v0.5.5 - **Frozen main:** `25a9cf1be6d245fbd7373cb1160dbc790baf5bd5` - **Reviewed candidate:** `8380c1f8ead8816bcf1f4ea9f66aa08e2441b15a` - **Previous desktop release:** `desktop-v0.5.4` - **Proposed immutable tag:** `desktop-v0.5.5` This PR must be **squash merged** only after the Desktop Release Candidate check passes. The branch must remain based directly on current `main`; stale base, payload drift, incomplete notes, or an unauthorized merge produce no tag. The checked-in changelog accounts for every non-merge commit in the release range. Publication remains bound to the immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com> |
||
|
|
25a9cf1be6 |
feat: paste composer text without formatting (#4801)
## Summary - handle Cmd+Shift+V on macOS and Ctrl+Shift+V on Windows/Linux in the message composer - read plain text through the native Tauri/arboard clipboard path in packaged builds, with a browser-only Clipboard API fallback - re-enter ProseMirror's paste pipeline with populated `text/plain` clipboard data so selection, undo, multiline behavior, and paste observers remain intact - cover both platform mappings with rendered composer E2E tests that assert the native command path ## Testing - `pnpm test` — 4,286 passed - `pnpm check` - `pnpm typecheck` - `pnpm exec playwright test composer-selection-formatting.spec.ts --project=smoke` — 26 passed - `cargo check --manifest-path desktop/src-tauri/Cargo.toml --workspace --all-targets --target aarch64-apple-darwin` - `just desktop-tauri-test` — 2,206 core tests plus integration and doc-test groups passed - full pre-push hooks passed ## Manual verification Physical packaged-app clipboard verification remains recommended on macOS, Windows, and Linux. The automated E2E uses mocked Tauri IPC but asserts the native `read_clipboard_text` command is invoked. Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> |
||
|
|
79c52166cf |
Revert "chore(release): release Buzz Desktop version 0.5.5" (#4808)
Reverts block/buzz#4800 |
||
|
|
a0ed13de14 |
chore(release): release Buzz Desktop version 0.5.5 (#4800)
## Buzz Desktop release v0.5.5 - **Frozen main:** `4a2305170eef565bf1836e2859247e67c030f8af` - **Reviewed candidate:** `2d03d37b05b68186b2caad9da79080032be3ac72` - **Previous desktop release:** `desktop-v0.5.4` - **Proposed immutable tag:** `desktop-v0.5.5` This PR must be **squash merged** only after the Desktop Release Candidate check passes. The branch must remain based directly on current `main`; stale base, payload drift, incomplete notes, or an unauthorized merge produce no tag. The checked-in changelog accounts for every non-merge commit in the release range. Publication remains bound to the immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com> |
||
|
|
4a2305170e |
fix: reauthenticate databricks model discovery (#4008)
## Summary - preserve Databricks catalog 401 responses as authentication failures and retry discovery exactly once after silently refreshing the rejected bearer - preserve runtime OAuth recovery: when discovery has no usable OAuth credential, `session/new` succeeds with only the trimmed configured model so the first `session/prompt` can run the existing browser PKCE flow - reject a rejected configured `DATABRICKS_TOKEN` with actionable, non-interactive guidance; static credentials cannot recover through PKCE - use the configured-model fallback for non-auth discovery failures without caching failed or fallback catalogs, so later sessions retry discovery - keep known Databricks v2 models only for authenticated empty-catalog responses and mark their provenance - resolve discovery before MCP spawn or session registration, preventing failed discovery from leaking resources or consuming session capacity - permit serialized interactive PKCE only from the explicit saved-agent model picker; passive draft discovery never opens a browser ## Runtime flow 1. OAuth discovery attempts cached credentials and silent refresh without opening a browser. 2. If no usable OAuth bearer exists, `session/new` advertises only the configured model and succeeds. 3. The first `session/prompt` uses `TokenSource::bearer()`, which may launch browser PKCE. 4. A later session retries discovery and caches only the authenticated catalog. ## Regression coverage - rejected-but-locally-fresh OAuth bearer performs one refresh and one catalog retry - OAuth mode with no cached token allows `session/new` and returns exactly the trimmed configured model - the OAuth fallback is not cached; a later authenticated session retries discovery and caches the returned catalog - rejected static tokens still reject `session/new` - failed discovery does not consume the sole session slot or spawn the supplied MCP process - Desktop interactive/passive auth intent, static-token redaction, and authenticated empty-catalog provenance ## Verification - `cargo test -p buzz-agent` - `cargo test --manifest-path desktop/src-tauri/Cargo.toml --lib commands::agent_models` - `cargo clippy --manifest-path desktop/src-tauri/Cargo.toml --all-targets -- -D warnings` - `cargo fmt --all -- --check` - `git diff --check` - full pre-push hooks ## Review Adversarial review found and drove fixes for session/MCP resource leakage, duplicate concurrent PKCE flows, sensitive error propagation, incorrect 403 reauthentication, missing discovery-level coverage, passive browser launch, and the Desktop file-size ratchet. The final follow-up preserves the existing prompt-time OAuth flow while retaining static-token rejection and pre-allocation discovery ordering. --------- Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> |
||
|
|
8faf09f9ae |
Revert "chore(release): release Buzz Desktop version 0.5.5" (#4797)
Reverts block/buzz#4788 |
||
|
|
4c665aeac3 |
fix(desktop): serialize tray channel actions for frontend (#4762)
## Summary - serialize native `openChannel` tray actions with the camelCase field names consumed by the TypeScript frontend - prevent a valid tray channel ID from becoming `/channels/undefined` - add a Rust serialization contract test covering the complete frontend payload shape ### Root cause `TrayAction` renamed the enum variant to `openChannel`, but its struct fields still serialized as `channel_id` and `community_generation`. The frontend reads `action.channelId`, so tray navigation called `goChannel(undefined)`. ### Testing - manually verified the corrected runtime payload and tray navigation before removing temporary logging - `just desktop-ci` - pre-push hooks (desktop checks/tests, Tauri checks, and Rust tests) --------- Signed-off-by: Kalvin Chau <kalvin@block.xyz> Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Co-authored-by: npub122y0pqkertljmedu303rl0aqrj3w8pvu43t6jxm6875lzg6f2pwqegc3xc <5288f082d91aff2de5bc8be23fbfa01ca2e3859cac57a91b7a3fa9f12349505c@buzz.block.builderlab.xyz> Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> |
||
|
|
b948c54792 |
chore(release): release Buzz Desktop version 0.5.5 (#4788)
## Buzz Desktop release v0.5.5 - **Frozen main:** `383d9e1eafd569b44b9c835200dba69ef7cec9dc` - **Reviewed candidate:** `ac589061ef1009f55384536e483cfe9b1260697b` - **Previous desktop release:** `desktop-v0.5.4` - **Proposed immutable tag:** `desktop-v0.5.5` This PR must be **squash merged** only after the Desktop Release Candidate check passes. The branch must remain based directly on current `main`; stale base, payload drift, incomplete notes, or an unauthorized merge produce no tag. The checked-in changelog accounts for every non-merge commit in the release range. Publication remains bound to the immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com> |
||
|
|
e30db7028f |
feat(projects): support multiple repositories (#4671)
## Summary - adopt the finalized NIP-MP project model so one project can enumerate and switch between multiple NIP-34 repositories - add project and repository navigation, activity summaries, existing-repository attachment, and repository access-channel management - preserve privacy-safe activation provenance for agent-authored patches, pull requests, issues, and associated commits ## Test plan - [x] Run desktop typecheck and unit tests - [x] Run focused NIP-MP, repository access, and provenance tests - [x] Run Rust formatting and desktop lint checks - [x] Run the complete pre-push suite after merging current `main` - [ ] Manually verify project creation, repository attachment, switching, and access repair on staging - [ ] Manually verify public-channel and private-agent origin labels on newly created Git activity Related: [#4695](https://github.com/block/buzz/pull/4695) --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
ce3cf3cd25 |
Polish Huddle voice controls (#4694)
## Summary - add a visible Stop control for interrupting agent speech - make push-to-talk available by default while preserving manual mute controls - refine agent management, muted audio states, drawer layering, and return navigation - suppress duplicate notification sounds for Huddle messages ## Why Huddles could trap users behind long agent speech, hide useful agent controls, and leave temporary Huddle state visible after the call. The drawer also regressed when the terminal substrate began painting behind the rounded app surface. ## Validation - `just desktop-ci` - focused Huddle Playwright coverage for the drawer, speech interruption, agent picker, and leave navigation --------- 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> |
||
|
|
5179726737 |
fix(local-archive): default both archive settings to enabled (#4750)
## 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>
|
||
|
|
0c33a8a55f |
fix(agents): canonicalize stale persona harness pins (#4631)
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> |
||
|
|
f86dfc5883 |
feat(desktop): surface config diff in restart-required badge (#3637)
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 [#3652](https://github.com/block/buzz/pull/3652)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
|
||
|
|
f18a9cb106 |
Defer desktop media uploads until send (#4522)
## 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 #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> |
||
|
|
631b05c883 |
feat: ship Buzz Term (#4347)
## 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> |
||
|
|
b29c8cdaa4 |
feat(desktop): redesign the Huddle experience (#4281)
## 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> |
||
|
|
985cdcc6ea |
feat(agents): model-tuning parity in global Agent Defaults editor (#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>
|
||
|
|
d4a4570b97 |
fix(desktop): clarify inherited agent parallelism (#4010)
## 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> |
||
|
|
651f637275 |
chore(release): release Buzz Desktop version 0.5.4 (#4562)
## Buzz Desktop release v0.5.4 - **Frozen main:** `6de85fe31d781122756aecf954bae7d357a56b9a` - **Reviewed candidate:** `5836cb8f0af478ed3ee3bc6464a20fa4cc91303f` - **Previous desktop release:** `desktop-v0.5.3` - **Proposed immutable tag:** `desktop-v0.5.4` This PR must be **squash merged** only after the Desktop Release Candidate check passes. The branch must remain based directly on current `main`; stale base, payload drift, incomplete notes, or an unauthorized merge produce no tag. The checked-in changelog accounts for every non-merge commit in the release range. Publication remains bound to the immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com> |
||
|
|
80315ac1a6 |
fix(desktop): harden Windows installs against Defender block and orphaned Node (#4382)
This PR fixes two Windows-specific install failures: Windows Defender blocking the bare `irm|iex` PowerShell install command, and managed Node shims pointing at a version-bumped (now-absent) Node directory. The Defender block (Trojan:Win32/Commando.A!ml) fires before PowerShell runs and is not clearable via Allow. The Node orphaning means shims in the managed npm prefix resolve but fail at runtime with 'node not recognized' because they reference the deleted old Node path. - Replace all three Windows CLI install commands (Goose, Claude, Codex) with a two-step shape — `Invoke-RestMethod` to a named temp file, then execute — to eliminate the dropper signature; a new `windows_install_command!` macro in `discovery/windows_install.rs` generates all three strings at compile time so the shape cannot drift between runtimes - `$ErrorActionPreference='Stop'` aborts on download failure instead of falling through to a missing-file exit-0; `exit $LASTEXITCODE` propagates the vendor script's own exit code - Add `probe_node(executable, expected_version, timeout)` as a bounded seam: stdout goes to a temp file (not a pipe) so no exit path can block on an inherited handle; the child runs in its own process group on Unix so an unconditional group SIGKILL on every exit path terminates all descendants; on Windows `taskkill /T /F` provides the same tree-wide cleanup; `managed_node_runtime_ready()` is a thin wrapper that resolves the managed Node path and calls the seam - Add `resolve_adapter_path()` in `managed_node.rs`: resolves the candidate first, then calls `should_invalidate_adapter()` — a pure predicate that returns `true` only when the resolved path is under `buzz_managed_npm_bin_dir()` AND the managed Node runtime is orphaned; external adapters outside the managed prefix are always preserved Note: CI cannot reproduce the Defender block (no live Defender ML classifier). Proof of fix is structural — the command shape no longer matches the dropper signature. Canary validation on a real Windows machine with Defender enabled is the definitive check. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> |
||
|
|
01c80aa9b3 |
fix(desktop): save key backups to authorized path (#4022)
**Category:** fix **User Impact:** Users can save password-protected identity backups directly to protected macOS folders such as Downloads. **Problem:** Signed macOS builds could not save a portable `.ncryptsec` backup to Downloads because the atomic writer created an unauthorized sibling temporary file. This surfaced as an “Operation not permitted” error after the user completed backup creation. **Solution:** Portable exports now write only to the exact path authorized by the native Save panel, sync and verify the saved bytes, and refuse to truncate an existing backup. Buzz’s app-managed backup retains its atomic writer and durability guarantees. <details> <summary>File changes</summary> **desktop/src-tauri/src/commands/export_util.rs** Clarifies that secret exports use a dedicated writer compatible with native Save-panel authorization. **desktop/src-tauri/src/commands/identity.rs** Routes portable NIP-49 exports through the Save-panel-compatible writer while preserving canonical app state. **desktop/src-tauri/src/key_backup.rs** Adds an exclusive-create portable writer with owner-only permissions, disk sync, byte verification, and cleanup on failure. Keeps the existing atomic writer for app-managed backups. **desktop/src-tauri/src/key_backup_tests.rs** Covers portable export permissions, absence of sibling files, and preservation of existing backups. </details> ## Reproduction steps 1. Install a signed macOS build containing this change. 2. Open **Settings → Profile → Private key → Create backup** and complete backup creation. 3. Save a fresh `identity.ncryptsec` file into `~/Downloads` and confirm Buzz reports success. 4. Open and verify the saved backup with its password. 5. Repeat the save using an existing filename and confirm Buzz preserves the existing file and asks for a new filename. ## Verification - Full desktop Tauri suite: 2,049 passed, 14 ignored - Diagnostic suite: 3 passed - Focused backup coverage: 30 passed - Tauri clippy (`--all-targets -D warnings`), Rust formatting, and `git diff --check`: passed - Push hooks: org safety, branch skew, and desktop Tauri checks passed Signed-production Downloads smoke remains required after merge because the signing workflow is restricted to `main`. Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz> |
||
|
|
f810a2f49e |
fix(desktop): make OpenAI key re-enterable after first save in card mint dialog (#4140)
Fixes a write-once dead-end in the card mint dialog where a user with an
expired OpenAI key had no way to replace it.
**Source-aware key status (Rust + TypeScript).** `card_mint_key_status`
returns a layer discriminant (`"none" | "global" | "persona" | "agent" |
"process"`) instead of a boolean. A pure `resolve_key_layer()` helper in
`card.rs` owns the classification logic; `card_mint_key_status`
delegates to it, so the production path is under direct test with no
duplicate logic.
**Mint form always reachable.** The key panel replaces the mint form
only for `none` (first-time setup) or when the user explicitly opens the
edit panel (`editingKey`). Keys from agent/persona/process layers show
an inline provenance row on the mint form with a "Why?" affordance;
clicking it shows the read-only redirect in a panel with a Cancel button
that returns to the mint form — never a terminal state.
**Precise auth-error matching.** The 401 handling in `cardMintStore.ts`
matches `startsWith("Card mint failed (HTTP 401 ")` plus the specific
`Incorrect API key` text, so avatar-fetch 401 errors pass through
unchanged.
**Tri-state key status row.** "Using your saved OpenAI key · Update"
renders only when `keyLayer === "global"` (confirmed writable key).
Query pending or errored hides the row without asserting key existence.
**Real tests.** Panel visibility derivations live in
`cardMintKeyUtils.ts`, which `AgentCardMintDialog.tsx` imports directly.
Tests cover all layers including the mint-reachability invariant (Mint
reachable for every resolved layer; only `none` gates setup).
- `card.rs` — new `resolve_key_layer()` pure helper;
`card_mint_key_status` delegates to it; 999 lines (under the 1000-line
ratchet)
- `card/tests.rs` — precedence test calls `resolve_key_layer()` directly
(no test-local closure); adds process-layer and blank-value cases
- `tauriPersonas.ts` — `CardMintKeyLayer` type; updated
`cardMintKeyStatus` signature
- `cardMintKeyUtils.ts` — `showKeyPanel`, `showReadOnlyRow`,
`showCancelButton`, `keyPanelTitle`, and helpers; component imports all
of them
- `AgentCardMintDialog.tsx` — inline provenance rows for all key
sources; key panel only for setup/edit; no unused variables
- `cardMintStore.ts` — precise 401 prefix matching
- `e2eBridge.ts` — `card_mint_key_status` stub returns `"global"` (not
boolean)
- Tests: 3959 JS passing, 2089 Rust passing, `tsc --noEmit` clean
Related: [block/buzz#4406](https://github.com/block/buzz/pull/4406)
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1ng3jzsaqxdhrfq22dg85j3lpr0zsh3jp7g2h9jyxl59wraayapnsu6kvfg <9a232143a0336e34814a6a0f4947e11bc50bc641f21572c886fd0ae1f7a4e867@buzz.block.builderlab.xyz>
|
||
|
|
be95a8a986 |
fix(config-bridge): add harness-definition env tier and fix equal-value model override (#3580)
All seven normalized config fields resolve through sanitized
`InheritedConfigTiers` passed wholesale to `read_config_surface`. The
reader's precedence tiers now match spawn's Layer 2b exactly — including
harness-definition env — and the equal-value model-override regression
is fixed.
## Changes
**`config_bridge/types.rs`** — add `InheritedConfigTiers`: persona env,
global env, harness definition env, structured model/provider/prompt for
both tiers. Add `HarnessDefault` `ConfigOrigin` variant for
harness-definition env values.
**`commands/agent_config.rs`** — `build_inherited_tiers` now resolves
the harness definition env using the same lookup path as spawn
(`record.runtime` → `persona.runtime` → empty string) and applies
`sanitize_inherited_env` to it. `resolve_config_surface` is unchanged in
shape — tiers passed to the reader now include `definition_env`.
**`config_bridge/reader.rs`** — `env_candidates` extended to 4-element
return (record, persona, global, definition). All five field builders
that use env candidates now include the definition-env slot below global
env and above the structured block, matching spawn Layer 2b. Magic
`configured[..6]` slice replaced with `configured[..configured.len()-1]`
(named split: all non-file candidates). Equal-value model-override arm
falls through to the normal resolve path instead of early-returning
`RuntimeOverride`, so the panel shows the baseline origin (e.g.
`BuzzExplicit`) rather than a spurious "Live override" label for a no-op
switch.
**`config_bridge/reader_tests_ext.rs`** — three new Layer 2b tests:
definition env beats structured persona model, global env beats
definition env, reserved-key-absent fallthrough.
**`commands/agent_config_tests.rs`** —
`genuine_explicit_live_switch_to_same_model_yields_clean_field` updated
to assert `origin == BuzzExplicit` (not `RuntimeOverride`); wrapped in
`with_no_goose_config` for hermeticity. New
`reserved_key_in_definition_env_shaped_map_is_stripped_by_sanitize` test
pins the shared sanitization contract.
**`AgentConfigPanel.tsx` / `types.ts`** — `HarnessDefault` origin
variant wired end-to-end: TS union type and provenance sentence
("Inherited from harness definition").
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
|
||
|
|
318fbf896e |
fix(security): bump nostr crates for RUSTSEC-2026-0225..0232 + default sprig image to published digest (#4392)
## What Two changes, both fallout/follow-up from #4289 landing: ### 1. Fix the Security job failing on main (lockfile-only) Eight RUSTSEC advisories published today against the nostr stack turned `cargo-deny check` advisories red on main ([failing run](https://github.com/block/buzz/actions/runs/30761611723/job/91533106673)). Not introduced by #4289 — the advisories landed upstream and any push to main today would have tripped them. - **RUSTSEC-2026-0225..0230** → `nostr` 0.44.6 → **0.44.7** (Debug output exposing NIP-46/NIP-60 credentials; wallet parsers accepting unauthenticated events; NIP-44/NIP-04/NIP-98 resource exhaustion; NIP-50 empty-filter panic) - **RUSTSEC-2026-0231..0232** → `nostr-relay-pool` 0.44.2 (root) / 0.44.1 (tauri) → **0.44.3** (auth-challenge memory exhaustion; processing of unverified relay events) Both workspace lockfiles bumped (`Cargo.lock`, `desktop/src-tauri/Cargo.lock`). No manifest changes. ### 2. Default the desktop GUI's sprig image to the published `ghcr.io/block/buzz-sprig` The first main-push after #4289 published the image publicly (package created 18:44Z, visibility `public`). The `config_schema()`'s `image` property now carries a `default`: ``` ghcr.io/block/buzz-sprig:sha-6530b58@sha256:17facfc7608d8ddb33bc056c9aaba1098f4ef6abe5655702fbfd7584d1f74d76 ``` **Why tag+digest, not tag:** the backend deliberately rejects tag-only references — the pod runs with the agent's nsec and tags are mutable pointers (`image.rs` §Image). The tag+digest form keeps the human-traceable `sha-6530b58` while the digest does the pinning; `image::parse` already normalizes it to the tagless canonical form, so create-intent fingerprints are identical to the bare-digest spelling. The digest is the **multi-arch manifest-list digest** (amd64+arm64), resolved via `docker buildx imagetools inspect`. **This is a UI prefill, not a baked fallback:** `image` stays in the schema's `required` list, an empty value still fails closed with a named field, and the desktop submits the value explicitly in `provider_config` (the `WhereToRunSection` probe seeds `providerConfig` from schema defaults) — so deploy fingerprints never depend on compiled-in provider state, and the spec's §K8s pod-reconciliation concern about baked-default divergence is not engaged. Module prose that said "no published image exists yet" is updated to match reality. No desktop code changes needed: the form already prefills from `properties[*].default` and submits seeded defaults. ## Testing - `cargo-deny check` at head: **advisories ok, bans ok, licenses ok, sources ok** (was: advisories FAILED) - `cargo test -p buzz-backend-kubernetes`: **158 passed** (154 lib + 4 wire), including new `schema_default_image_round_trips_through_parse` pinning the constant + its normalization, and the wire `info` test now asserting the default is present in the provider's real stdout response - Live provider probe: `{"op":"info"}` against the built binary returns the default in `config_schema.properties.image.default` with `required` unchanged (`["namespace","image"]`) - Full workspace test suite via pre-push hook: green (earlier direct `cargo test --workspace` run: sole failure was `api::mesh_demo::demo_join_forwarded_arm_round_trips_echo`, the documented pre-existing main flake — unrelated, fails on base) - Image existence verified against GHCR: `docker buildx imagetools inspect ghcr.io/block/buzz-sprig:sha-6530b58` resolves to the pinned manifest-list digest with linux/amd64 + linux/arm64 manifests --------- Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> |
||
|
|
f86cfc7369 |
fix(desktop): back/forward via keyboard chords, mouse X1/X2 buttons, and swipe gestures (#3778)
## Problem Two related gaps in global back/forward navigation. Fixes #3775. 1. The keyboard shortcuts almost never fire in real use — users fall back to clicking the toolbar chevrons and assume the shortcuts don't exist. 2. On macOS, mouse back/forward buttons (X1/X2) and horizontal swipe gestures do nothing, although they navigate in every browser and in Slack. **Duplicate check:** searched open PRs and issues — none found beyond #3775 (filed alongside this fix). #3078 / #3377 are next/previous-*channel* navigation, a different feature. ## Root causes **Keyboard:** `useBackForwardControls`'s keydown handler bailed whenever the event target was editable — but `useComposerAutofocus` deliberately focuses the message composer (a ProseMirror contenteditable) on mount and on every channel switch. In steady state focus almost always lives in the composer, so the chords were silently swallowed. Invisible to CI because `navigation.spec.ts` only ever clicked the `global-back` / `global-forward` buttons, never pressed the keys. **Mouse/swipe:** on macOS, WKWebView never delivers X1/X2 button events or swipe gestures to the page (Safari handles them natively in the app layer, not in page JS), and Buzz had no native handler. ## Fix ### Keyboard chords (web layer) Match the existing platform chord regardless of the event target and drop the editable-target guard: - `⌘[` / `⌘]` have no text-editing semantics in macOS text fields, and the TipTap/StarterKit editor config binds no `Mod-[` / `Mod-]` shortcuts (checked `useRichTextEditor.ts` — list indentation is Tab/Shift-Tab). - `preventDefault()` keeps the chord out of the editor — asserted in the e2e test. This matches browsers and Slack, where back/forward chords work while a text field is focused. Chord matching is extracted into a pure helper, `app/navigation/backForwardChords.ts`, so it can be unit tested; behavior (bindings, modifier exclusivity, `code`-based matching for non-US layouts) is unchanged. ### macOS mouse buttons and swipe gestures (native layer) An NSEvent local monitor in `mouse_nav.rs` catches what the webview can't see and emits a `mouse-nav` Tauri event to the main window (`emit_to`, so navigation stays scoped if multi-window ever lands) that the frontend acts on. Two AppKit event shapes map to navigation: - `otherMouseUp` with button 3/4 — mice whose X1/X2 buttons arrive as plain button events. These are swallowed after emitting so nothing downstream double-handles them. - `swipe` with a horizontal delta — AppKit's page-swipe gesture (`swipeWithEvent:`): `deltaX > 0` back, `deltaX < 0` forward. Sent by mouse drivers that synthesize a page-swipe gesture for the back/forward buttons instead of button-3/4 events (the hardware this was verified on). Stock Apple trackpad and Magic Mouse swipes arrive as phased scroll-wheel events instead, which this PR does not handle — that path (`ScrollWheel` + `trackSwipeEventWithOptions:`, which also needs scroll-edge detection) is deferred to a follow-up. Swipes are passed through (swallowing mid-gesture events could confuse AppKit gesture tracking). The swipe path was verified end to end on hardware whose back/forward buttons emit only swipe gestures, never button-3/4 events — an instrumented event monitor confirmed the events arrive as `NSEventType::Swipe` with `deltaX ±1`, and navigation worked after mapping them. ## Tests - **13 unit tests** for the web-side chord matcher (`backForwardChords.test.mjs`): supported chords, modifier exclusivity, `code` fallback, and preservation of line-editing shortcuts. - **6 Rust unit tests** for the native mapping helpers (`mouse_nav.rs`): button 3/4 directions, other buttons ignored, swipe delta sign → direction, zero-delta (gesture-begin) ignored. - **e2e regression case** in `navigation.spec.ts`: presses the platform chord *while the composer is focused* — the missing coverage. Verified it fails against the pre-fix implementation and passes with the fix. - Full desktop unit suite: 3832/3832 pass. Full Rust suite (`cargo test`, buzz-desktop): 1888 passed / 0 failed. `pnpm typecheck`, `biome check`, `pnpm check`, `cargo fmt --check`, `cargo clippy`: clean (no new warnings). - Full Playwright e2e: 958 passed; 6 failures are relay-infrastructure tests (live relay seeding / relay state seam) that fail identically without this change — `navigation.spec.ts` is fully green. ## Manual test 1. Open a channel, then another (composer autofocuses on each switch). 2. `⌘[` — returns to the previous channel; `⌘]` — forward again. Typing `[` / `]` in the composer inserts normally. 3. Mouse back/forward buttons navigate the same way, from anywhere in the window (verified on macOS on hardware using both event shapes). ## Update — 2026-07-31 Removed the redundant DOM mouse-button handler after verifying it was unnecessary. The native macOS path remains unchanged and was revalidated manually. --------- Signed-off-by: npub1yvnq5equak5errqpku8stskushny9wsvt0fc2ywcpwt79yslwaqswe7tse <23260a641ceda9918c01b70f05c2dc85e642ba0c5bd38511d80b97e2921f7741@buzz.block.builderlab.xyz> Signed-off-by: Matheus Iser <matheusiser@squareup.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1yvnq5equak5errqpku8stskushny9wsvt0fc2ywcpwt79yslwaqswe7tse <23260a641ceda9918c01b70f05c2dc85e642ba0c5bd38511d80b97e2921f7741@buzz.block.builderlab.xyz> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
6530b58a61 |
feat(k8s): Kubernetes backend plugin + desktop deploy path (#4289)
# Kubernetes backend plugin (crates/buzz-backend-kubernetes) + desktop deploy path Implements docs/remote-agents.md (merged @ |
||
|
|
3ade48d503 |
fix(desktop): point Oh My Pi preset at omp.sh (#3516)
## Summary Points the Oh My Pi preset at the `omp.sh` installation page instead of the GitHub repository. The project serves its current installer from `omp.sh/install.sh`. ### Related issue Extracted from the maintainer request in #3111. I found no matching open pull request in a final duplicate check. ### Testing `https://omp.sh/` returned HTTP 200 with the installation page. `https://omp.sh/install.sh` resolved to the current installer and returned HTTP 200. `cargo test --manifest-path desktop/src-tauri/Cargo.toml preset_entry -- --nocapture` passed 5 tests. `just ci` passed. This changes metadata only, so screenshots do not apply. Signed-off-by: Shreyash Vengurlekar <262980978+kiranmagic7@users.noreply.github.com> Co-authored-by: Shreyash Vengurlekar <262980978+kiranmagic7@users.noreply.github.com> |
||
|
|
fa1a5b1a79 |
fix(mesh): stop restarting a busy or loading shared-compute node (#3909)
## Problem Sharing compute with a large model (e.g. `gemma-4-26B`) put the desktop app into a **restart loop**: toggle Share → app appears to "download" / stall → the whole app restarts → repeat. Small models (E4B) were unaffected, which made it look model-specific and flaky. It is not model-specific and not flaky. It is a **false-positive liveness check**. ## Root cause (proven by black-box measurement) A `serve` node's OpenAI ingress (`:9337`) serializes **all** HTTP — including the `/v1/models` liveness probe — behind the current in-flight inference. It is *also* HTTP-unresponsive during model load and package-layer download. In every one of those phases the node is alive and progressing, but it cannot answer an HTTP probe. Measured on a standalone `gemma-4-26B` node (randomized ~30k-token prompt, cache-miss): | during one ~30s inference | result | |---|---| | concurrent `GET /v1/models` | **27.0s**, then 200 | | concurrent small `/chat/completions` | **28.8s**, then 200 | | `tcp_connect(:9337)` throughout | **~0ms** | Both HTTP calls simply queued behind the turn; TCP kept accepting instantly. A probe with any timeout shorter than the turn reads the node as dead. Buzz then acted on that false "dead" reading in two places, **both restart paths added in #2823**: 1. **Ingress watchdog** — after 2 consecutive `/v1/models` timeouts, evicts the node; for a serve node eviction means `app.request_restart()`. Two dead probes landing inside a prefill window → restart loop. 2. **Start / restore paths** — on a `wait_for_mesh_inference` timeout, `stop()` the node and (fresh start) `request_restart()` the app "to guarantee cleanup" — even though the node was still loading weights or downloading layers. This is the exact line in the incident log: `started node failed inference readiness … Buzz is restarting`. ## Fix Treat a **bound TCP port as alive**. Death has exactly one unambiguous signal: a *closed* port. - **Watchdog** (`recovery.rs`): only `PortClosed` may evict. A bound-but-HTTP-unresponsive `Unhealthy` port is never evicted, at any probe streak or urgency. Closed-port eviction is unchanged. - **Start / restore** (`commands/mesh_llm.rs`): install the runtime **before** probing readiness (so it is always tracked by `AppState` and can never be orphaned — which is what the restart was guarding against), and on a readiness timeout **leave it warming up** instead of stopping/restarting. Launch-restoration stays disarmed until real inference is confirmed, so a genuinely broken start is retried next launch rather than silently disabling Share Compute. ### What this deliberately does *not* do Detecting a node that is bound-but-internally-wedged needs a liveness signal that bypasses the inference lock. There is none today, so this fix cannot distinguish "wedged" from "busy" and errs toward not restarting. That gap is a mesh-llm bug, filed upstream: **Mesh-LLM/mesh-llm#1126** (lock-free `/live`+`/ready` on the ingress). A follow-up here can consume it once it lands. ## Tests - Watchdog never evicts a bound/busy port at any probe streak or urgency (the regression). - Closed-port eviction still fires (dead listener still reclaimed). - Black-box: a listener that accepts TCP then stalls HTTP classifies as `Unhealthy`, not `PortClosed`. - **Mutation-proven**: reverting the eviction rule to the old count-based logic fails the busy-node test. `cargo test` (desktop, `--features mesh-llm`) green, fmt + clippy clean. ## Not covered here The intermittent nature means I could not force the live loop deterministically on a warm machine; the proof is the measured serialization + the mutation-proven unit/black-box tests. Live behaviour (app no longer restarts while a 26B node loads/serves) still merits a manual check before merge. --------- Signed-off-by: Michael Neale <michael.neale@gmail.com> Co-authored-by: Michael Neale <michael.neale@gmail.com> |
||
|
|
eb049ddf81 |
feat(desktop): Agent Trading Cards — mintable agent-snapshot card PNGs with optional NIP-44 lock (#3278)
## Agent Trading Cards "Create Agent Card" action in the agent panel that mints an AI-generated trading card PNG which **is** the agent: the card carries the `buzz_agent_snapshot` tEXt chunk and is drag-in importable like any snapshot PNG. ### What's in here - **Mint pipeline (Rust):** one OpenAI Responses call — `gpt-5.6-sol` as card designer with `gpt-image-2` via the `image_generation` tool (~2–3 min). New `mint_agent_card` / `save_agent_card` commands; preview with reroll; save or send as `.agent.png` with round-trip verification before any bytes leave the app. - **Snapshot/chunk work stays in Rust,** reusing the existing encoder/decoder seams (byte-compat golden vector proves the plain path is identical to the pre-envelope encoder for placeholder, PNG-injection, and JPEG-transcode paths). - **Locked cards (NIP-44):** optional `buzz-agent-snapshot-encrypted` envelope encrypted to the (owner, agent) pair. `parse_canonical_pubkey` performs lift-x curve validation before any API spend; wrong-key decrypt returns a fixed refusal; the plain decoder refuses locked cards. - **Guardrails:** 10 MiB ceiling on final bytes, memory structurally `none` in the snapshot, full-manifest import disclosure, API-key hygiene via env layering (record > persona > global > process), fail-early validation ordering (all key/lock/NIP-44-cap checks before Responses spend). - **Import side:** full-manifest disclosure dialog, locked-card import disclosure, bounded avatar fetch. ### Review Code reviewed by Wren across the full arc; final locked-card cross-review **APPROVED 9/9/9** at exactly this head (`64f819dc8`), with independent same-SHA verification: Rust lib 1,843/1,843, clippy `--all-targets -D warnings`, desktop file-size gate. ### Live-mint evidence (real API, shipping seams, this SHA) - **Plain (Honey):** 188s, 1500x2250, 5,101,503 bytes (< 10 MiB); decoded manifest == built manifest; memory=none. - **Locked (Fizz):** 176s, 4,670,184 bytes; owner-key and agent-key decrypt both verified via logical manifest compare; wrong-key refusal exact; plain decoder refuses. - **Live finding:** built-in agents' ~171 KB inline avatars exceed the NIP-44 65,535-byte plaintext cap and the fail-early guard fires before API spend — clean error path, noted as a UX follow-up for large-avatar agents choosing lock. Full evidence (cards + dialog screenshots) posted in the originating thread. --------- Signed-off-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Co-authored-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> |