mirror of
https://github.com/block/buzz.git
synced 2026-10-31 06:21:34 +00:00
f4efd99d43fcd6cfc4f31fd650db4b7f73dee2db
1516 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f4efd99d43 |
feat(desktop): relocate permission-policy default to the agent definition
The editable permission-policy control belonged on the instance-edit surface, but that surface is being retired and a definition-scoped default is the correct home for an authority grant that describes how a persona should behave. Add the definition default as resolver tier 2 (instance override → definition → global → built-in `ask`), live-resolved through the same total `resolve_effective_permission_policy` signature every spawn, summary, and deploy path already carries — never mint-copied, so a definition-default change moves every linked instance on the next summary. It is written through `apply_persona_behavior` as part of the behavior group and is local-only: never published to the catalog and never mint-copied, unlike `respond_to`/`parallelism`. The instance surface (`AgentPermissionPolicyField`) becomes display-only: it shows the resolved effective value, its source, and the per-instance applied-vs-desired drift row, which stays instance-scoped because only a deployed instance has a byte-stamped deploy receipt. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
0bc5683e52 |
fix(desktop): enforce deploy-receipt invariant for applied permission policy
The applied permission policy was extracted from the deploy payload via .ok(), so a missing or unparseable launch.policy_env value stamped a silent None on a successful deploy — suppressing the drift row and defeating the field. The prior regression test manually seeded applied_permission_policy and would have passed even with the production stamp deleted. Extract the applied policy through extract_applied_permission_policy before the provider is invoked; a broken payload invariant now fails the deploy instead of stamping None. record_deploy_success/record_deploy_failure make the receipt transitions explicit: success stamps the exact sent value, redeploy updates it, failed redeploy retains the last confirmed value. Discriminating receipt and transition tests replace the seeded regression, plus a TSX render test for the drift row and an AGENTS.md contract entry. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
ca9acfa36a |
fix(desktop): exact-allowlist permission kinds, disclose persistent deny
PermissionDecisionButtons classified actionable options via kind.startsWith("reject"), so an unknown or malformed reject-prefixed kind (e.g. reject_later_v2) still rendered a trusted Deny button whose semantics the UI does not understand. Replace prefix matching with an exact recognized-kind allowlist (allow_once, reject_once, reject_always); any kind outside it fails closed and renders no action. reject_always stays actionable (persistent denial only reduces authority) but its label defaults to "Always deny" when the harness omits one, so a user never silently chooses persistence.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
|
||
|
|
00456e462a |
fix(desktop): persist applied permission policy for remote deploys
ManagedAgentSummary recomputed the displayed permission policy from the mutable agent record plus global config, so flipping the global default after a remote deploy made the UI report a policy the worker was not actually running. Stamp the byte-identical policy sent to the provider onto the record at the deploy choke point, expose it on the summary as applied vs desired, and surface drift in the policy field UI with a redeploy prompt. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
3ad999a618 |
fix(desktop): fail-closed PermissionDecisionButtons for allow_always and unknown kinds
allow_always rendered as an ordinary green Allow button, giving the observer feed a one-click path to a durable grant — the same UX concern Wes flagged in B2. Unknown kinds also rendered as green Allow (Wes: should fail closed). Tighten PermissionDecisionButtons: - allow_once → actionable green Allow button (unchanged path) - reject_* family → actionable red Deny button (unchanged path) - allow_always → non-actionable amber badge: "Permanent grant — use request card". The thread card (D5 disclosure) is the correct surface; the observer feed is advanced/debug and should not offer one-click durable grants. - Unknown kinds → silently omitted (fail closed); no button rendered. Add LifecycleActivity.render.test.mjs with five cases covering allow_once, reject_once, allow_always (badge present, no button), unknown kind (nothing rendered), and a mixed allow_once + allow_always card. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
ff5b21f374 |
chore: merge origin/main into duncan/permission-policy
Pull in #5406 (banner-dismiss fix) and successful_steer_deliveries field added to TaskMeta. Resolves conflicts in lib.rs and pool.rs by taking both permission_decision_tx (branch) and successful_steer_deliveries (main) as distinct fields — each is independently required. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.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> |
||
|
|
44456e200e |
fix(desktop): resolve overlapping member mentions (#5225)
## Why Selecting or typing a member whose display name extends another member's name, such as `@Fast Fizz Codex`, could emit p-tags for both identities and wake the wrong agent. ## What - Resolve overlapping member-name matches by choosing the longest valid display name at each mention offset - Preserve separately typed short-name mentions at different offsets - Add regression coverage for selected team expansions and manually typed prefix collisions ## Risk Assessment Low to medium — this changes Desktop mention routing only. Exact mentions and distinct offsets remain supported; same-length ambiguous display names remain conservatively tagged because text alone cannot disambiguate them. Will resolve https://github.com/block/buzz/issues/2909 Generated with Goose Signed-off-by: Hardworking Honey <c5c455215c2506cb8ba776518cec804af62d3a0526e32d496a22072e395042b9@buzz.block.builderlab.xyz> Signed-off-by: Atish Patel <atish@squareup.com> Co-authored-by: Hardworking Honey <c5c455215c2506cb8ba776518cec804af62d3a0526e32d496a22072e395042b9@buzz.block.builderlab.xyz> |
||
|
|
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> |
||
|
|
97aa9e3185 |
fix(desktop): preserve Welcome banner dismissal (#5406)
## Summary - remove the complete Welcome guidance surface when dismissal reaches `hidden` - preserve dismissal across the private and starter Welcome channels for the active identity - assert the starter channel's actual `welcome-everyone` title on re-entry ## Why PR #5330 introduced two deterministic Desktop E2E failures: - the inner banner unmounted, but `welcome-composer-guidance-layer` remained - the re-entry test expected case-sensitive `Welcome` while navigating to `welcome-everyone` The state hook also scoped completion to channel IDs while `ChannelPane` remounts during navigation. The Welcome guidance is one experience spanning both Welcome channels, so completion now survives that remount while remaining identity-scoped. ## Validation At `b577eb42edffe889f63566f2457eacea720f3593`: - `pnpm -C desktop typecheck` - focused Biome check for all four changed files - E2E build - both `welcome-everywhere banner` integration tests repeated three times: **6/6 passed** - mandatory pre-push desktop check, typecheck, and full desktop unit suite: **4,535 passed** - `git diff --check` Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
60c247e20e |
test(e2e): align permission outcome assertion with label-based rendering
Test 03 asserted /Approved.*allow_once/ — the old format that rendered the raw ACP kind string. describePermissionOutcome now returns the harness-provided label (or verb-only fallback); the legacy non-ask path has no label map, so the rendered text is 'Approved'. Production code is untouched; this test file is the only change. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
f858cabb26 |
fix(desktop): sentinel-only edit gate in formatTimelineMessages; reword card doc
- formatTimelineMessages: after isAuthorizedMessageEdit passes, add sentinel
check — if target content parses as a permission sentinel (v:1), only an edit
signed by the original agent (byte-equal to target.pubkey) may replace the
body. Owner/attacker edits of sentinels are silently dropped, leaving the
authenticated pending body and editSignerPubkey unset. Generic owner-edit
behavior for non-sentinel messages is unchanged.
- 4 integration regression tests in formatTimelineMessages.test.mjs drive the
full formatTimelineMessages → computePermissionRequest path:
1. agent-signed pending + owner-signed resolved edit → pending preserved
2. edit-before-original arrival order → same result
3. attacker-signed edit → pending preserved
4. agent-signed resolved edit → card retires to resolved
- permission-request-card.tsx:1-15: reword doc comment from stale fence-protocol
framing to version-1 bare-JSON sentinel; document wire format and the new
formatTimelineMessages edit gate as a security invariant
- All 4637 desktop tests pass; all 6 gates green
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
|
||
|
|
31299008bc |
test(desktop): add harness integration fixture test for kind-9 parser
Embeds the exact kind-9 content string produced by buzz-acp's build_sentinel_pending_payload (captured via kind9_content_fixture_structural_invariants in crates/buzz-acp/src/acp.rs). Verifies that extractPermissionRequest and isPermissionRequestSentinel accept the bytes the harness actually emits, closing the harness→desktop wire boundary check. Two new tests in the 'harness integration fixture' suite: - test_harness_kind9_content_parses_to_pending_payload - test_harness_kind9_content_identified_as_sentinel Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
a16b059836 |
fix(desktop): parse bare-JSON sentinel, unify expiry clock, tighten guards, add component tests
- extractPermissionRequest: replace fence parser with JSON.parse; sentinel is bare JSON object with v:1 (no code-fence wrapper) - isPermissionRequestSentinel replaces stripPermissionRequestSentinel; MessageRow suppresses markdown body for agent sentinel messages - PendingPermissionRequestCard owns single nowSecs tick; ExpiryCountdown and PermissionButtons share same clock — split-state expiry bug eliminated; click handler rechecks expiry at call time - Type guards: outcome narrowed to union literal; expiresAt must be non-negative integer; originalEventId validated as 64-char lowercase hex; chosenOptionId non-null iff outcome===applied; every advertised optionId must have a label entry - PermissionRequestCardBlock.test.mjs: 7 component tests (owner/non-owner/forged/ edit-auth/expiry) with jsdom + @testing-library/react + fake timers - permissionRequest.test.mjs: raw JSON fixtures (no wrap helper), 22+ rejection cases per frozen schema invariants, isPermissionRequestSentinel test suite - computePermissionRequest.test.mjs: raw JSON fixtures, non-owner viewer path, replay/archive resolved state, past expiresAt accepted at parse time - All 4631 desktop tests pass; all 6 gates green Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
74c9200ad8 |
Merge remote-tracking branch 'origin/main' into duncan/permission-policy
* origin/main: fix(desktop): welcome banner overlap and missing dismiss control (#5330) fix(desktop): prevent horizontal clipping in Prompt Context modal (#5324) chore(release): release Buzz Relay version 0.2.1 (#2856) chore(release): release Buzz Desktop version 0.5.8 (#5326) fix(buzz-agent): recover from 400-shaped image rejections; unbound benchmark agent rounds (#5318) Revert "fix(acp): reject unattended permission requests" (#5323) feat(desktop): unify add agent flows (#5015) fix(buzz-agent): budget summarizer reasoning separately so it cannot starve the handoff summary (#5248) infra: bind development services to loopback (#4871) chore(release): release Buzz Desktop version 0.5.7 (#5252) fix(desktop): isolate relay admission tests (#5221) fix(desktop): externalize boot <style> to prevent Tauri CSP nonce override (#5242) fix(desktop): let imported and recovered identities finish onboarding (#5228) Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
d808c4bdbf |
feat(acp): publish kind-9/40003 permission sentinel cards into channel thread
Implements Phase-1 of issue #4938: the harness now publishes kind-9 sentinel cards into the channel thread when a session/request_permission reaches the Ask policy arm, and edits them to resolved state (kind-40003) when the permission concludes. Sentinel publish system (buzz-acp): - Add relay_publisher, agent_relay_keys, agent_owner_pubkey_hex, turn_initiator_pubkey, sentinel_channel_id, sentinel_thread_reply_id fields to AcpClient; wired per-turn from pool.rs and lib.rs. - D7-final admission check: Ask only fires for owner-initiated turns when relay_publisher is present; non-owner turns silently downgrade to reject so no card is posted for unattended sessions. - kind-9 sentinel published after inserting the pending entry; event ID stored in PermissionEntry::sentinel_event_id for the edit. - kind-40003 resolved edit published in finish_permission Ok path; skipped when sentinel_event_id is None (relay-absent sessions). - Both publishes are fire-and-forget; permission flow never blocks on relay acceptance. - publish_event_acked / PublishEventAcked / AckOutcome scaffolding preserved for future acked-publish callers. Label fix (desktop): - describePermissionOutcome: accepts optionLabels (display strings) and optionKinds (for deny/approve verb only) as separate maps; never renders raw ACP kind strings to the user. - describePermissionTerminalReason: builds optionLabels from label ?? name fields; raw kind string falls back to verb-only ("Approved" / "Denied") when no display string is available. - Legacy key path passes empty labels map (verb-only fallback). Documentation (NIP-AO.md): - Permission Sentinel Cards section: kind-9/40003 lifecycle, D7-final admission check, D5 durable-rule disclosure, sentinel authenticity. Tests: - 736 + 9 buzz-acp unit tests pass (sentinel publish skipped via relay_publisher: None in test helpers; D7 gate bypassed when relay_publisher is None). - agentSessionTranscriptPermissions.test.mjs: 12 named tests for describePermissionOutcome and describePermissionTerminalReason. - computePermissionRequest.test.mjs: tests for sentinel card parser. - Updated expectations in agentSessionTranscript.test.mjs and ingestArchivedObserverEvents.test.mjs to match verb-only fallback. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
cef78d723d |
feat(desktop): render permission-request sentinel card in thread timeline
Implements the desktop side of issue #4938: a kind-9 sentinel published by buzz-acp is parsed and displayed as an interactive card in the thread view. Parser (permissionRequest.ts): - Discriminated union PermissionRequestPending | PermissionRequestResolved aligned to the frozen schema (event b31c716e): requestNonce, sessionId, turnId, optionIds[], labels{}, hasDurableRule, durableRuleNote, outcome, chosenOptionId, originalEventId. - All untrusted display strings capped at 200 chars; optionIds bounded to 10. - extractPermissionRequest() and stripPermissionRequestSentinel() are render-safe (never throw). Compute helper (computePermissionRequest.ts): - D1 signer gate: kind-9 must be signed by the channel's known agent pubkey. - Edit authenticity: resolved state accepted only from kind-40003 signed by the original agent (not owner, not attacker). - selectProseOrPermission() mirrors selectProseOrNudge() from configNudge. Card (permission-request-card.tsx): - Pending: renders option buttons from optionIds + labels[optionId]. Deny-style button heuristic uses label text (opaque optionIds carry no semantic). ExpiryCountdown ticks down to expiresAt; expired cards show 'Timed out'. D5 durable-rule disclosure (durableRuleNote) shown below buttons when hasDurableRule is true. - Resolved: shows outcome label derived from outcome + chosenOptionId. - Actionable buttons gated: isOwner && state === 'pending' only. - sendPermissionDecision() is fire-and-forget; button disables on click with 'Decision sent'; re-enables on relay error so the user can retry. Integration (MessageRow.tsx, formatTimelineMessages.ts, types.ts): - editSignerPubkey threaded from formatTimelineMessages through TimelineMessage so the edit authenticity gate has the raw event signer without re-querying. - PermissionRequestCardBlock wraps useIdentityQuery to resolve the current viewer, computes isOwner, and renders the card when a trusted sentinel is present. React.memo with custom comparator keeps MessageRow re-render budget clean. Auth helper (permissionRequestAuthPubkey.ts): - getPermissionRequestAgentPubkey() returns the agent pubkey only for known-agent-signed kind-9 events, mirroring getConfigNudgeAuthorPubkey. Tests: 33 named tests covering all 6 frozen fixtures verbatim plus rejection cases (bad version, unknown state, size bounds, malformed JSON, field invariants) and the full signer/edit-authenticity gate matrix. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
f029deafae |
fix(desktop): welcome banner overlap and missing dismiss control (#5330)
## Problem The `WelcomeComposerGuidanceLayer` in the `#Welcome` channel was positioned with `absolute inset-x-0 bottom-full z-[-1]` — outside the `composerWrapperRef` measurement boundary. `useComposerHeightPadding` observes `composerWrapperRef`'s block size to set `paddingBottom` on the timeline scroll container, but the absolutely-positioned layer didn't contribute to that size. The banner sat directly on top of the newest message, blocking the thread affordance on that message, and had no manual dismiss control. ## Fix **Overlap**: Changed `WelcomeComposerGuidanceLayer` from `absolute inset-x-0 bottom-full z-[-1]` to `relative` (in normal flow). As a normal-flow child of `composer-dock`, the layer's full height is now measured by the ResizeObserver and fed into the timeline's `paddingBottom`, so the newest message is always fully visible and its thread affordance is always clickable while the banner shows. **Dismiss**: Added an `X` close button (`data-testid="welcome-composer-dismiss-button"`) on the prompt state. Clicking fires `onDismiss`, which drives `dismissing → hidden` immediately (same slide-down animation as the auto-dismiss path) and marks the channel ID as completed in the session ref so the banner does not reappear on channel re-entry within the session. **Refactor**: Extracted the banner state machine (refs, timers, `useEffect`s, and callbacks) from `ChannelPane.tsx` into `useWelcomeComposerBanner.ts`. This keeps `ChannelPane.tsx` well under the 1000-line file-size ratchet and makes the state machine independently testable. ## Changed files - `desktop/src/features/channels/ui/WelcomeComposerBanner.tsx` — `WelcomeComposerGuidanceLayer` positioning fix; `onDismiss` prop; dismiss button; `overflow-hidden` / `mb-0` / `flex-1` cleanup - `desktop/src/features/channels/ui/ChannelPane.tsx` — remove inline banner state machine, use `useWelcomeComposerBanner` hook, pass `onDismiss` - `desktop/src/features/channels/ui/useWelcomeComposerBanner.ts` — new hook owning all banner state --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Hayt <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz> |
||
|
|
fbf89e3bed |
fix(desktop): prevent horizontal clipping in Prompt Context modal (#5324)
The Prompt Context modal (observer feed → check icon under sent messages) was clipping all content and card right-padding at the dialog edge. **Root cause**: `PromptContextDialog` renders inside `DialogContent`, which is a CSS grid. The child flex wrapper had default `min-width: auto`, so the widest unbreakable token in the content (64-char hex event IDs, `Tags: [[...]]` JSON) set the grid track width, blowing it past `max-w-xl`. `overflow-hidden` then clipped everything at the dialog edge — including the section cards' right padding. **Fix**: - `AgentSessionTranscriptList.tsx`: add `min-w-0` to the `flex max-h-[85vh] flex-col` wrapper so the grid item can shrink below its max-content width. - `PromptSectionAccordion.tsx`: replace `wrap-break-word` with `wrap-anywhere` on the body text (open and collapsed states) and the title. `overflow-wrap: anywhere` reduces min-content width, which `break-word` does not, letting long tokens wrap inside the cards rather than inflating the track. The `line-clamp-2` collapsed preview is preserved unchanged. Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> |
||
|
|
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> |
||
|
|
02f640bc45 |
feat(desktop): unify add agent flows (#5015)
**Category:** improvement **User Impact:** Users can create, discover, and import agents from one consistent Add agent dialog. **Problem:** Agent creation, discovery, and import were split across a dropdown and separate dialogs, making the Add agent flow fragmented. The existing E2E suite also continued targeting the deleted dropdown after the flows were unified. **Solution:** Route the new-agent card directly into a unified dialog with dedicated Create, catalog, and Import navigation, then update the affected E2E coverage to exercise that interface and its current empty state. <details> <summary>File changes</summary> **desktop/src/features/agents/ui/AgentDefinitionDialog.tsx** Supports rendering the agent definition form inside the unified Add agent experience while retaining the standalone dialog behavior. **desktop/src/features/agents/ui/AgentDefinitionDialogShell.tsx** Adds the shared shell used to present agent-definition content consistently in embedded and standalone contexts. **desktop/src/features/agents/ui/AgentDialog.tsx** Passes the revised dialog state and close behavior through the existing agent dialog entry point. **desktop/src/features/agents/ui/AgentsView.tsx** Connects the Agents page to the unified Add agent dialog and opens newly added catalog agents in their profile panel. **desktop/src/features/agents/ui/PersonaCatalogDialog.tsx** Combines catalog browsing, agent creation, and snapshot import behind persistent navigation, including dirty-navigation confirmation. **desktop/src/features/agents/ui/UnifiedAgentsSection.tsx** Replaces the new-agent dropdown with a direct Add agent entry point and adjusts the responsive card grid. **desktop/src/features/agents/ui/personaLibraryCopy.ts** Updates catalog-facing copy for the unified experience. **desktop/src/features/agents/ui/usePersonaActions.ts** Returns the resolved local persona after catalog activation so the caller can open the added agent. **desktop/tests/e2e/agent-readiness-screenshots.spec.ts** Opens the embedded create pane directly for readiness screenshots. **desktop/tests/e2e/agents.spec.ts** Covers unified Create, catalog, and Import navigation and asserts the current shared-agent empty state. **desktop/tests/e2e/global-agent-config-screenshots.spec.ts** Updates global configuration screenshot setup for direct create-pane entry. **desktop/tests/e2e/inline-custom-harness.spec.ts** Updates custom harness setup for the embedded create form. **desktop/tests/e2e/persona-env-vars.spec.ts** Updates environment-variable and model-provider scenarios for direct create-pane entry. **desktop/tests/e2e/persona-model-combobox-screenshots.spec.ts** Updates model combobox screenshot setup for direct create-pane entry. **desktop/tests/e2e/smoke.spec.ts** Updates agent-creation smoke coverage for the unified Add agent dialog. **desktop/tests/e2e/where-to-run-config.spec.ts** Updates provider-selection coverage for the embedded create form. </details> ## Reproduction steps 1. Open the Agents page and select the new-agent card. 2. Confirm the Add agent dialog opens directly on Create without an intermediate dropdown. 3. Use the left navigation to browse shared agents and open Import. 4. Select a catalog agent and confirm the dialog closes and the added agent's profile panel opens. 5. Run the affected desktop Playwright smoke and integration specs and confirm all scenarios pass. --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> |
||
|
|
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> |
||
|
|
a5a9240241 |
fix(desktop): let imported and recovered identities finish onboarding (#5228)
**Category:** fix **User Impact:** People who onboard by importing an existing key or recovering from a phone can now use "Skip for now" (and Next) on the harness setup and model config steps, instead of getting stuck. **Problem:** On the "Set up your agent harnesses" and "Configure your default model settings" onboarding steps, clicking **Skip for now** — or **Next** — did nothing for anyone who reached those steps by importing an existing key or recovering an identity from a phone. The app stayed frozen on the step. **Solution:** The onboarding state machine sets `continuingPubkeyRef` to the current pubkey on import/recovery to keep the flow on `onboarding` until setup finishes (added in #4845). But `complete()` never cleared that ref, so once it matched the current pubkey the stage stayed pinned to `onboarding` forever — completion could never win. `complete()` now clears the ref so finishing/skipping actually settles the flow. Fresh-generated keys never set the ref, which is why first-run fresh-key skip already worked and the gap went unnoticed. <details> <summary>File changes</summary> **desktop/src/features/onboarding/machineOnboarding.ts** Clear `continuingPubkeyRef` inside `complete()` so an imported/recovered identity's "continuing" marker no longer outlives completion and pin the stage to `onboarding`. **desktop/tests/e2e/onboarding.spec.ts** Add a regression test that imports an existing key, reaches harness setup, clicks **Skip for now**, and asserts onboarding exits (reaches community onboarding). This fails without the fix. The existing skip tests only exercised the fresh-key path, which never set the ref — hence the gap. </details> ## Reproduction steps 1. Start onboarding and choose **Use an existing key** (or recover from a phone); import a key and continue to **Set up your agent harnesses**. 2. Click **Skip for now** (or **Next**). Before this change, nothing happens — the step is stuck. The same trap hits **Configure your default model settings**. 3. With this change, Skip/Next advances out of onboarding as intended. 4. Automated: `pnpm build:e2e && pnpm exec playwright test onboarding.spec.ts --project=integration -g "imported-key users can skip out of harness setup"` — passes with the fix, fails without it. ## Root cause Introduced by #4845 (`feat(identity): recover desktop identity from a signed-in phone`), which added `continuingPubkeyRef.current === currentPubkey` as an independent condition selecting the `onboarding` stage. That guard has no off switch: `complete()` set the completion flag but never cleared the ref, so the OR'd condition kept the stage pinned. Not a revert candidate — the guard's intent (keep a just-published identity in onboarding until setup finishes) is correct; it just needed to release on completion. Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
e87f265d25 |
fix(desktop): use struct literal init to satisfy clippy::field_reassign_with_default
Four test-setup sites created GlobalAgentConfig::default() then immediately assigned permission_policy. Clippy 1.95 flags this as field-reassign-with-default. Rewrite each site to use a struct literal with ..Default::default(). Files: agents_deploy.rs (1 site), permission_policy.rs (3 sites). Zero semantic change — test-only correction. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
2dd0706d8f |
refactor(desktop): extract RawManagedAgent/fromRawManagedAgent to managedAgentMapping.ts
tauri.ts exceeded the file-size ratchet after main's #3818 moved editMessage out, shrinking the allowance from 1175 to 1161. The six permission-policy additions to tauri.ts that were within the old headroom now exceed the tighter baseline. Extract RawManagedAgent type and fromRawManagedAgent function into a dedicated shared/api/managedAgentMapping.ts module (mirrors main's editMessage.ts pattern). tauri.ts re-exports both for zero caller changes. Removes now-unused imports (ManagedAgentBackend, PermissionPolicy, PermissionPolicySource, RawRestartDiffEntry). Result: tauri.ts 1073 gate vs 1161 allowed (88-line margin). Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
fe901d8300 |
Merge remote-tracking branch 'origin/main' into duncan/permission-policy
* origin/main: (32 commits) Recover from max-token response truncation (#5223) chore(release): release Buzz Desktop version 0.5.6 (#5214) fix(mobile): keep latest messages above composer (#4981) fix(sdk): preserve self-mention p tags in message and forum event builders (#4975) bump @tauri-apps/cli to ~2.11.4 to fix linux app icon issue (#4858) feat(desktop): adding rich link previews to messages (#3818) fix(buzz-agent): Responses reasoning summary, Anthropic display:summarized, ACP v2 messageId (#5195) fix(desktop): retain distinct agent instances in autocomplete (#5202) fix(desktop): defer channel visibility change to Save (#5203) feat(desktop): Projects follow-ups — access restrictions, fast loading, activity feed polish (#5073) refactor(cli): replace probe/decider/detail split with single typed extractor (#5191) fix(desktop): drop unhandled rejection from throwing window.Notification (#5143) fix(desktop): fence localStorage SecurityError from killing the React tree (#5142) fix(desktop): make terminal output selectable (#4980) fix(desktop): use WEBKIT_DMABUF_RENDERER_FORCE_SHM for NVIDIA/AppImage (#3654) (#4505) Make public starter channels best effort (#5192) Mobile: add anchored reaction popover (#5025) feat(mobile): add bee pull-to-refresh (#5059) Remove agent creation success modal (#5063) fix(buzz-agent): escalate LLM timeouts per retry and log per-call latency (#5130) ... Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> # Conflicts: # desktop/src/shared/api/tauri.ts |
||
|
|
bcd9bacc97 |
refactor(desktop): extract permission/transcript types to fix file-size ratchet
Extract permission-related types from agentSessionTranscript.ts to agentSessionTranscriptPermissions.ts, from tauri.ts to tauriEditMessage.ts, and from types.ts to permissionPolicy.ts. Refactor AgentPermissionPolicyField to a self-managing forwardRef component and extract useRespondToField hook to OwnerOnlyAccessField.tsx to bring AgentInstanceEditDialog.tsx under its cap. Trim doc comments on new permission fields. Fix &mut borrow on apply_permission_policy_update call in agent_models.rs. All nine ratcheted files are now at or below their allowances. Zero semantic change — all exports and behavior are preserved via re-exports from the original modules. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
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> |
||
|
|
c3c39cc263 |
bump @tauri-apps/cli to ~2.11.4 to fix linux app icon issue (#4858)
## Summary <!-- What does this change and why? --> #3419 is a tauri bug (https://github.com/tauri-apps/tauri/issues/15110), which is already fixed in https://github.com/tauri-apps/tauri/pull/15596. All we need is bump the @tauri-apps/cli version to include the bug fix. ```sh pnpm update --filter ./desktop @tauri-apps/cli@2.11.4 ``` This pr simply includes the changes after running the update command. ### Related issue <!-- Fixes #1234, or N/A. Before opening: search existing issues/PRs for duplicates — link the closest one, or say "none found". --> fix #3419 close #3436. this pr supersedes it. ### Testing <!-- How was this verified? UI change? Include before/after screenshots (or a short recording). --> build the appimage and check the symlink in the appimage using `unsquashfs`. ```sh $ unsquashfs -o 944632 -ll /tmp/buzz/desktop/src-tauri/target/release/bundle/appimage/Buzz_0.5.4_amd64.AppImage | grep -i dirIcon lrwxrwxrwx root/root 8 2026-08-04 21:54 squashfs-root/.DirIcon -> Buzz.png ``` Signed-off-by: Tsung-Han Yu <14802181+johan456789@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>
|
||
|
|
e9925db54e |
fix(desktop): retain distinct agent instances in autocomplete (#5202)
## Summary - preserve each distinct agent pubkey in autocomplete even when agents share a persona or owner/name - continue to collapse duplicate source rows for the same normalized pubkey - show a truncated pubkey in the channel member-add picker so same-named instances are selectable ## Validation - `pnpm --filter buzz test` — 4,489 passed - `pnpm --filter buzz exec tsc --noEmit --pretty false` - `pnpm --filter buzz exec biome check src/features/agents/lib/agentAutocompleteEligibility.ts src/features/agents/lib/agentAutocompleteEligibility.test.mjs src/features/channels/ui/MembersSidebar.tsx` - independent validation by Fast Fizz on `509cb8d97b82f9708e24d4d59ad17c7b39516643`: typecheck, focused Biome, 22/22 focused tests, and `git diff --check` Generated by Hardworking Honey. --------- Signed-off-by: Hardworking Honey <c5c455215c2506cb8ba776518cec804af62d3a0526e32d496a22072e395042b9@buzz.block.builderlab.xyz> Co-authored-by: Hardworking Honey <c5c455215c2506cb8ba776518cec804af62d3a0526e32d496a22072e395042b9@buzz.block.builderlab.xyz> |
||
|
|
ef2ecaf873 |
fix(desktop): defer channel visibility change to Save (#5203)
## Problem In the **Edit channel** dialog, flipping visibility (Public <> Private) persisted **immediately on selection**, bypassing the **Save changes** button — while every other field (name, description, temporary, TTL) waited for an explicit save. This surprised users and gave no chance to cancel a flip, e.g. a private->public change that instantly exposes channel history. Reported in the Buzz "Welcome" channel by Kevin Chung. ## Root cause The visibility dropdown was wired to `handleConvertVisibility()`, which called the update mutation on selection. This was intentional at the time (there was even an e2e test named `02 — visibility updates immediately` and an "Updating…" spinner), but it is inconsistent with the rest of the dialog and is the surprising behavior reported. ## Change (defer to Save) - Visibility becomes a **deferred draft** like the other fields: selecting a value updates local `isPrivateDraft` and marks the draft dirty. The change commits via `handleSaveChannelEdits` (which already handled visibility) on **Save**, and is discarded on **Cancel**. - The dialog title now reflects the **pending draft** (`nextVisibility`), so the pending choice is visible before saving. - The edit-dialog reset restores `isPrivateDraft` from server state. - Removed the now-dead `handleConvertVisibility` handler, `isConvertingVisibility` state, the `channelIdRef` race guard it needed, and the unused `isPending`/"Updating…" spinner path in `ChannelPermissionsSettings` (no caller passes `isPending` anymore). ## Tests - Rewrote e2e `02` -> **`visibility defers to Save`**: select -> Save enabled -> title reflects draft -> Save -> persists; toggling back to the original value clears the draft and disables Save. - Extended `09` (cancel discards drafts) to also cover a visibility change. - Repurposed `10`: the stale-update race it guarded is architecturally gone, so it now asserts an **unsaved visibility draft does not leak across a channel switch**. ## Validation - `pnpm typecheck` — clean - `biome check` (changed files) — clean - `pnpm test` — **4497 passed / 0 failed** - `playwright test --project=smoke channel-controls` — **10 passed** Signed-off-by: Kevin Chung <chung@squareup.com> Co-authored-by: Fizz <e3f95089179cc1bcc68d70c334b9bdf670d0470496db90bcdbb20386963432da@buzz.block.builderlab.xyz> |
||
|
|
fb73561e64 |
feat(desktop): Projects follow-ups — access restrictions, fast loading, activity feed polish (#5073)
## Summary Follow-up batch on the Projects overview (continues merged #1677): - **Repository access restrictions** — repositories the viewer can't reach are surfaced with a reason instead of failing silently. Channel-ACL denials (which arrive as the same 404 as a missing repo, for anti-enumeration) are re-classified using the repository's channel binding and the viewer's memberships (`useRepositoryAccess.ts`, `projectRepoAvailability.ts`). - **Projects loads in seconds instead of minutes** — enumeration no longer crawls every kind:5 deletion event on the relay. It fetches project/repo announcements first, then queries deletions scoped to those coordinates via chunked `#a` filters (3 queries instead of hundreds on staging). - **Activity feed layout polish** — bare event-type glyph beside the headline (no badge circle), timeline spine runs through the avatars connecting consecutive cards, linkable actor/project names are bold in theme foreground, rounded hover state, alignment fixes. - **Create button pinned** — the "+" create menu is pinned to the pane's top-right corner (equal 16px insets) and no longer scrolls away with the page header. - **List controls as a table header** — the scope selector (left) and sort + layout toggle (right) render as the first row of the list container on the Projects/Repositories/PRs/Issues tabs; in card view the identical bar stands alone with the cards below (`ProjectsListHeaderBar.tsx`). - **Repository rows show the git location** — subtitle is `github.com/org/repo` for external repos or `owner/repo` (resolved profile name) for Buzz-hosted ones, instead of repeating the project name (`repositoryDisplayPath`). - **Uniform work-item row heights** — issue rows previously ran the author chip in inline flow, letting the 20px avatar grow the line box ~3px taller than PR rows; both lists now share the same flex subtitle. 📸 Screenshots: [feed layout / pinned button](https://github.com/block/buzz/pull/5073#issuecomment-5214114069) · [list header / repo subtitles / row heights](https://github.com/block/buzz/pull/5073#issuecomment-5217480712). Note: two empty `chore: retrigger CI` commits exist on the branch from working around the Aug 6 GitHub Actions incident; happy to drop them with a signoff rebase before undrafting if preferred. Latest `main` is merged in (`a0cc35220`). ## Test plan - [x] Desktop unit tests (4,493 pass after merging main), Biome, tsc - [x] New unit tests for scoped deletion enumeration and repo availability re-classification - [x] New unit tests for `repositoryDisplayPath` (external, Buzz-hosted, unresolvable) - [x] Screenshot verification of feed layout, connector spine, and pinned button (top + scrolled states) — posted to the PR - [x] Screenshot verification of the list header row (list + card), repo subtitles, and matching PR/issue row heights — posted to the PR - [ ] Manual pass against staging (projects list load time, restricted-repo states) --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> |
||
|
|
73f2ffed83 |
fix(buzz-acp): thread single nonce through sync denial paths; upgrade two tests to pipe-level proof
Thread one nonce through each synchronous denial path so the acp_read and acp_write telemetry frames always carry the same nonce. Before this change, emit_permission_read_non_actionable generated its own nonce internally while the caller passed a different nonce to finish_permission_sync, producing one logical challenge/answer pair with two different nonces. Desktop's nonce-only correlation rule left the read card live because the write could never find it by nonce. Fix: accept nonce as a parameter in emit_permission_read_non_actionable (removing its internal new_permission_nonce() call) and drop the now-dead caller_will_emit_read parameter from handle_permission_request and emit_permission_read_with_nonce. Upgrade two tests from telemetry-proxy assertions to direct pipe proofs: - ask_permission_entry_deadline_equal_to_loop_hard_deadline_writes_denial_before_exit: replace observer telemetry assertion with a capture script that reads the denial line from child stdin NDJSON and parses the wire response. - cancel_first_write_fails_stops_immediately_no_second_write: add a write-attempt counter (Arc<AtomicUsize> in write_ndjson_inner) to assert exactly ONE attempt was made and the loop stopped, not just that no successful writes occurred. Add Rust tests proving the nonce is shared: - sync_denial_malformed_options_read_and_write_carry_same_nonce - sync_denial_preflight_failure_read_and_write_carry_same_nonce Add TypeScript reducer tests: - buildTranscript_sync_denial_write_with_matching_nonce_retires_card - buildTranscript_sync_denial_write_with_mismatched_nonce_leaves_card_live Update NIP-AO schema prose and observer.rs field comment to explicitly document the permission_terminal exception to the authorization-only-on- acp_read/acp_write rule. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
e47894a133 |
fix(desktop): drop unhandled rejection from throwing window.Notification (#5143)
## Summary WebKit throws `NotificationError` from the `Notification` constructor when the notification backend becomes temporarily unavailable (measured repro attached to #5081). Every existing call site used `void sendDesktopNotification(...).then(...)` — discarding the returned promise with no rejection handler — so a throwing constructor became an unhandled promise rejection. The notification was silently dropped and the only trace was console noise. Closes #5081. ## What changed Fenced the throw at the source inside `sendDesktopNotification` (`desktop/src/features/notifications/lib/desktop.ts`): - A new `try { ... } catch { ... }` wraps `new window.Notification(...)` and the `onclick` attach. - On catch, we `console.warn` once and `return false`, so the promise the call sites discard is always fulfilled with the same boolean result. No caller needs to change. ## Why at the source and not at each call site The issue body lists four rejecting edges: `useAppShellDesktopNotifications` (2×), `useReminderNotifications`, `use-feed-desktop-notifications`. Patching them one-by-one leaves the door open for the next consumer to make the same mistake — and the function itself advertises `Promise<boolean>`, so callers are entitled to assume the promise resolves with the delivery bit rather than rejects. Fixing the inside satisfies both properties for every present and future caller. ## Test plan - Behavior change is a guarded return value around a single constructor; unit coverage is best expressed inside the mounted-hook harness already used in the repro. Existing notification helpers (`shouldNotify*.test.mjs`) continue to pass. - Full `just ci` runs on the blocker. - The next notification after a backend blip delivers normally (the throw is per-call, not sticky). ## Note on scope This addresses the titled bug: unhandled rejection from a throwing constructor. A separate, intended follow-up is to wire a user-visible delivery-miss event if the platform exposes one — that's notification-observability work, not a } catch. --------- Signed-off-by: iroiro147 <sarthak.singh@mastersunion.org> 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> |
||
|
|
8630e58eb0 |
fix(desktop): fence localStorage SecurityError from killing the React tree (#5142)
## Summary WebKit throws `SecurityError` from `localStorage.getItem` (not just `setItem`) when storage access is denied for the origin. With no `ErrorBoundary` in `desktop/src`, any such throw inside a provider render (`ThemeProvider`, `CommunitiesProvider`, `App` boot) propagated to the reconciler, unmounted the root, and left a blank window. Measured repro in #5078: a single throwing `getItem` on `buzz-communities` or `buzz-active-community-id` kills the container. Closes #5078. ## What changed **New helper — `desktop/src/shared/lib/safeStorage.ts`** - `getStorageItem(key, fallback?)` — wraps `window.localStorage.getItem`; on a thrown error (SecurityError under denied-storage origin) it warns once per key and returns the fallback. - `setStorageItem(key, value)` and `removeStorageItem(key)` — same fail-closed contract (return `false` on throw). - Unit tests in `safeStorage.test.mjs` cover the happy path and the `SecurityError` path. **Rewired the init-path readers that ran before any UI existed** - `desktop/src/features/communities/communityStorage.ts` — `migrateLegacyCommunityStorage`, `loadCommunities`, `loadActiveCommunityId`, `loadCommunityDiscoveryAfterLeave`, `initFirstCommunity` - `desktop/src/features/communities/legacyCommunityStorage.ts` — `migrateLegacyCommunityStorageBeforeRender` - `desktop/src/shared/theme/ThemeProvider.tsx` — `readStoredTheme`, `applyCachedVars`, the `useState` initialisers for `accentColor` and `followSystem`, and the accent re-read inside `applyTheme` **Root-level fence — `desktop/src/app/RootErrorBoundary.tsx`** - New top-level `ErrorBoundary` wrapping the whole provider tree in `main.tsx`. Any remaining uncaught render error (a future storage read that bypasses the helper, or any other render-time crash) renders a degraded splash with a Reload button instead of a blank window. ## Test plan - `desktop/src/shared/lib/safeStorage.test.mjs` — node `--test` runner, 11 assertions across healthy, absent, and SecurityError-throwing storage. - Full `just ci` runs on the blocker. - Existing `communityStorage.test.mjs` and `legacyCommunityStorage.test.mjs` continue to pass (they exercise the same functions via in-memory Storage doubles; the new code path in `migrateLegacyCommunityStorage` only adds a `try/catch` around the same body). ## Why not an ErrorBoundary-only fix A boundary alone can't help on a *clean* mount — the first throw already unmounted the whole subtree before any state or fallback data was loaded, so retrying would hit the same throw on the very next render. The storage accessor has to fail closed *and* the boundary has to exist for whatever bypasses it. Both are needed; neither is sufficient alone. --------- Signed-off-by: iroiro147 <sarthak.singh@mastersunion.org> 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> |
||
|
|
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> |
||
|
|
7d277dab4d |
fix(acp): address Thufir pass-4 review findings
- Every ask terminal routes through finish_permission(); applied path stops on false (poison) immediately instead of looping back. Cancel path uses None idle sentinel instead of dummy instant. - Deadline equality: process expired entries first at entry.deadline == hard_deadline, then return HardTimeout — fail-closed response is always written before exit. - Wire-truth tests: production-path, cancel, and nine-request tests now capture child stdin NDJSON and assert parsed exact lines/ids. Temporal tests rebuilt around one continuously running loop per scenario. - Desktop nonce-present = nonce-only: unknown nonce drops the frame without falling back to the id map. Legacy fallback keyed by compound (channel:session:turn:id), never bare id. Both indexes cleaned on every terminal (acp_write, permission_terminal) and backstop (turn_completed, turn_error). New tests: FOREIGN-nonce drop + cleanup assertions on both indexes for all four terminal paths. - NIP-AO: permission_terminal in frame-kind table; synchronous policy outcomes (rejected/allowed/allow_failed_closed) in reason table with explanatory note distinguishing ask vs. synchronous paths. - Desktop: permission_terminal handler uses pinned uncertain copy; tests for live replay and lifecycle-only archive replay. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
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> |
||
|
|
daa8877581 |
Make public starter channels best effort (#5192)
## Summary - treat public starter-channel provisioning as best-effort after preserving the required private Welcome path - let community onboarding complete and focus Welcome when the reported metadata lookup error occurs - remove the now-obsolete retry-toast expectations for optional starter provisioning ## Scope This intentionally does not change relay tombstone semantics or auto-join existing public channels. ## Test plan - `pnpm exec playwright test tests/e2e/deep-link-invite.spec.ts` (8 passed) - `pnpm exec playwright test tests/e2e/onboarding.spec.ts --grep "failed public starter channel setup"` (1 passed) - `pnpm typecheck` - `pnpm check` - `pnpm test` (4483 passed) - pre-push hook: branch-skew, desktop-check, desktop-typecheck, desktop-test passed on `4658a07beb1e1d54443da5cd2e4a28fae0232f24` --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
c8743b2f20 |
Remove agent creation success modal (#5063)
## Summary - remove the post-creation private-key modal - return directly to the underlying page with one “Agent created” toast - preserve failed channel-attachment retry through an actionable toast ## Validation - desktop checks and E2E build - 4,392 desktop unit tests - focused Playwright coverage for standard, customized, and attachment-retry creation flows Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
6607eaf55e |
fix(acp): structural round — one nonce-keyed permission record, finish_permission() helper
Implements Thufir's minimal shape across three passes of residual defects: Harness (acp.rs): - Remove PermissionEntryState::Resolved — entries are removed from the map on every terminal transition (applied/timed_out/cancelled). The absence of a nonce is the replay guard; no tombstones means capacity counts only live (Pending|Writing) requests, fixing the 9th-request-in-one-turn bug. - Add finish_permission() terminal helper owning Pending→Writing→Resolved for all terminals. Exactly one write+flush, exactly one nonce-correlated authorized acp_write with the terminal reason. Any write failure poisons the process and emits a permission_terminal observer-only event so Desktop can retire the card (the uncertain path). - Cancel path: write failure also poisons and emits permission_terminal. cancel-during-write emits permission_terminal for the in-flight entry. - Re-arm idle deadline when live pending count reaches zero so a slow human decision grants a fresh idle window instead of insta-cancelling the turn. - Capacity check now counts only live (Pending|Writing) entries. - Clippy: fix assert_eq!(x, true) → assert!(x), while-let-loop, doc overindented list items in acp.rs and config.rs. - Fmt: cargo fmt applied. Desktop (agentSessionTranscript.ts): - acp_write authorized frames correlate exclusively by authorization.requestNonce (primary); JSON-RPC id correlation is a legacy fallback for non-ask paths. - Terminal copy derives from authorization.reason (applied/timed_out/cancelled/ uncertain) via describePermissionTerminalReason — timeout now renders 'Timed out' not 'Denied (reject_once)'. - set actionable: false on all retirement paths. - permission_terminal observer event handler retires the card via nonce. - turn_completed and turn_error backstop: retireAllLivePermissionCards() retires any still-live cards so missing telemetry and archive replay cannot reconstruct live controls. - Biome format applied. lib.rs: - fit_observer_event_to_budget: early return without mutation when event.authorization.is_some() — authorized frames are never leaf-trimmed or stubbed (NIP-AO §3 byte-for-byte requirement). - Enqueue suppresses over-cap authorized frames entirely (defense in depth). - Test: test_authorized_frame_payload_is_never_trimmed. Tests: - ask_production_path_emits_request_captures_nonce_and_delivers_decision: real script emits session/request_permission, harness captures nonce from in-process observer, routes decision through channel, asserts end_turn. - cancel_writes_exactly_one_response_per_pending_id_no_replay: registers entries via production path, captures nonces before cancel, verifies each emitted cancel nonce matches a registered entry nonce, verifies no replay. - ask_permission_idle_is_suspended_while_pending_entry_exists: paused-time, asserts entry present at 299s. - ask_permission_deadline_fires_at_300_seconds: paused-time, asserts entry removed at exactly 300s. - ask_permission_idle_rearmed_after_last_entry_resolves: paused-time, proves idle deadline re-armed after decision applied. - ask_nine_sequential_requests_all_succeed_after_capacity_recovery: nine sequential requests each decided before the next is queued; asserts 9 distinct authorized acp_write observer nonces. NIP-AO.md: - session_resolved = session establishment (not terminal). - Added turn_completed and turn_error rows as terminal lifecycle events. - uncertain path: permission_terminal observer event replaces wrong session_resolved reference. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
cd2125c34b |
Improve video review readiness and controls (#5161)
   --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
c71f658539 |
Polish advanced agent setup and Welcome composer (#4926)
## Summary - move **Run on** into Advanced, directly after **Who can send instructions** - reuse the modal’s shared dropdown styling - give the Welcome guidance and composer matching glass treatment while preserving the corrected exit layering ## Validation - `pnpm -C desktop typecheck` - focused Playwright: Run on configuration (3 passed) - focused Playwright: Welcome onboarding flow (1 passed) - desktop unit suite (4,290 passed) --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Watcher <bb7abfd757d0af7b66569d02ab9c0316b616f9d0c151ecf5b964344c462e7f8f@buzz.block.builderlab.xyz> Co-authored-by: Watcher <bb7abfd757d0af7b66569d02ab9c0316b616f9d0c151ecf5b964344c462e7f8f@buzz.block.builderlab.xyz> |
||
|
|
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> |
||
|
|
f03de210cd |
fix(desktop): preserve authoritative agent avatars (#4984)
**Category:** fix **User Impact:** Agent cards and catalog listings now show the avatar belonging to the identity they represent. **Problem:** Running agent cards could show a stale definition avatar instead of the concrete agent profile, while adding another publisher's catalog entry could let local edits repaint that publisher's listing. This made agent identity look inconsistent across My Agents and the Agent Catalog. **Solution:** Treat the concrete agent pubkey profile as authoritative for running-card avatars, with the linked definition as fallback. Keep relay publications authoritative for foreign catalog presentation while using local copies only for linkage and selection state. | before | after | |--|--| | <img width="874" height="592" alt="Screenshot 2026-08-06 at 3 48 43 PM" src="https://github.com/user-attachments/assets/2cc6c9f7-ea50-413c-9c7b-4d34bd8b4ec7" /> | <img width="884" height="597" alt="Screenshot 2026-08-06 at 3 48 40 PM" src="https://github.com/user-attachments/assets/b14a865c-65c4-458f-9c30-d1a557c877d7" /> | | agent-set avatar not showing | agent-set avatar is showing | ## Changes <details> <summary>File changes</summary> **desktop/src/features/agents/lib/agentCardAvatar.ts** Adds the explicit avatar precedence rule for running agent cards and blocks avatar-dependent actions until the authoritative profile query settles. **desktop/src/features/agents/lib/agentCardAvatar.test.mjs** Covers profile precedence, definition fallback, blank avatar handling, and the profile-loading transition for linked-agent actions. **desktop/src/features/agents/lib/personaCatalogRelay.ts** Keeps publisher-provided catalog identity and behavior fields authoritative after a local copy is added. **desktop/src/features/agents/lib/personaCatalogRelay.test.mjs** Verifies local copies contribute linkage and selection without overriding publisher presentation. **desktop/src/features/agents/ui/UnifiedAgentsSection.tsx** Uses the concrete agent profile avatar before the linked definition avatar on running-agent cards. </details> ## Reproduction Steps ### Running agent card uses the agent profile avatar Use two visibly different, publicly reachable image URLs: **A** for the saved definition and **B** for the running agent profile. 1. In **Settings → Experiments**, enable **Agent-managed profiles**. This prevents Desktop from restoring the definition avatar over an agent's own relay-profile changes. 2. In **Agents**, create an agent with image **A** as its avatar and start it. 3. In a channel containing that agent, ask it to update its own Buzz profile avatar to image **B**. The exact CLI operation under the agent identity is `buzz users set-profile --avatar <image-B-url>`. 4. After the agent confirms the update, reopen **Agents → My Agents** (or reload the page so its kind:0 profile is fetched again). 5. Verify the running agent card shows image **B**, not definition image **A**. Open **⋯ → Share** and verify the share flow also uses image **B**. Before this fix, the My Agents card and share flow preferred image **A** whenever the linked definition had an avatar. ### Catalog listing remains publisher-authoritative This scenario requires a second Buzz identity so the entry is foreign to the account under test. 1. As the publisher identity, create an agent definition with a distinctive name, avatar, and instructions, then use **Share → Share to catalog**. 2. As the test identity, open **Agents → Discover agents**, find that publication, and add it. 3. In **My Agents**, open the added copy's **⋯ → Edit**, change its name, avatar, and instructions, and save. 4. Return to **Discover agents** and find the same publisher entry. 5. Verify it remains selected/added but still shows the publisher's original name, avatar, and instructions—not the test identity's local edits. ## Validation - `pnpm test` — 4,376 passed - `pnpm typecheck` — passed - `pnpm check` — passed with existing non-error notices --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
67b77344d6 |
fix(desktop): next/back navigation during key creation onboarding (#4978)
**Category:** fix **User Impact:** Users can navigate back while an identity key is being created, while Next remains visible and unavailable until creation finishes. **Problem:** The key-creation hold hid both navigation actions, leaving users without an escape route or a clear indication of what would happen next. **Solution:** Keep the onboarding footer mounted throughout creation, leave Back enabled, and gate Next on the completed identity state. <details> <summary>File changes</summary> **desktop/src/features/onboarding/ui/BackupStep.tsx** Keeps the onboarding navigation footer visible during key creation, with Back available and Next disabled until the identity is ready. **desktop/tests/e2e/onboarding-backup.spec.ts** Covers the loading and completed navigation states so the intended behavior cannot quietly crawl back out of the pit. </details> ## Reproduction steps 1. Start desktop onboarding and choose to create a new identity. 2. Submit the profile step and observe the key-creation screen. 3. Confirm Back is enabled while Next is visible but disabled. 4. Wait for key creation to finish and confirm Next becomes enabled. ## Screenshots | Before | After | | --- | --- | | Navigation actions are hidden during key creation. | Back remains enabled while Next stays visible and disabled. | |  |  | Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |