Skip to content
This repository was archived by the owner on Aug 17, 2026. It is now read-only.

fix: reset Hive to thin upstream parity - #37

Merged
100yenadmin merged 41 commits into
mainfrom
fix/36-thin-upstream-parity
Jul 26, 2026
Merged

fix: reset Hive to thin upstream parity#37
100yenadmin merged 41 commits into
mainfrom
fix/36-thin-upstream-parity

Conversation

@100yenadmin

@100yenadmin 100yenadmin commented Jul 26, 2026

Copy link
Copy Markdown
Member

Outcome

Resets Hive onto pinned upstream Buzz main at dd222a509b156ba52ed3219e895d7bf1cf322c92 and replays only the thin Electric/Hermes seams required by Hive #36.

Native Buzz startup, collaboration, settings, Huddles, drafts, channels, DMs, profiles, and product surfaces remain active. Electric OAuth selects and authorizes one company community; ordinary collaboration remains user-signed and relay-enforced.

Included

  • Hermes and hermes-acp runtime discovery with dependency readiness separate from launch-time ACP authentication discovery.
  • Durable ACP channel/session continuity and invalidation behavior.
  • Direct member disconnect plus durable membership revalidation capped at 15 seconds for delegated agents.
  • Bounded shared metadata polling for channel create/update and DM open.
  • Thin macOS Keychain identity plus revocable Electric desktop session adapter.
  • Native Agents catalog enriched by company-scoped VM agent public identities.
  • Hive branding, compatible deep links, package assets, and an isolated signed-fork updater contract.
  • Tested managed settings policy: hide only Compute, Hosted communities, and Mobile; restore native Agents, Templates, Experiments, Custom emoji, Local archive, and Updates.

Explicitly removed from the previous candidate

  • Managed channel/DM brokers and control-identity collaboration writes.
  • Duplicate collaboration screens.
  • Blanket native command guards and broad settings hiding.
  • Session-owned recipient identity.

Focused proof on behavior head ae735034cd47d165c21d2fc05d77ab6ac994fa7d

  • Managed auth/key custody and logout recovery: 22 Rust tests passed.
  • Native and managed desktop deep-link scheme routing: 2 focused Rust tests passed.
  • Hermes discovery/auth contract: 6 passed, 1 intentional subprocess fixture ignored.
  • Metadata read-after-write polling: 3 passed.
  • ACP durable session store: 6 passed.
  • ACP DM authorization including upstream fix(acp): restrict DM turns to owner and verified siblings block/buzz#2591: 4 passed.
  • Relay membership principal/agent disconnect tenant fence: 1 passed.
  • Desktop parity/product/agent/settings/onboarding tests: 52 passed.
  • Managed settings/company-agent catalog: 4 focused tests passed.
  • TypeScript typecheck, desktop checks, managed-feature Clippy, Rust format, file-size guard, and diff checks passed.
  • Independent semantic review and final targeted delta review passed; all nine GitHub review threads have terminal dispositions and are resolved.

The current PR head adds only a validated fork-portable Docker cache/image namespace correction after that behavior review.

Proof boundary

This draft PR is source and focused-test evidence only. It does not claim merge, signed artifact, deployment, installed two-user canary, VM mutation, customer rollout, or public release. Durable identity/session-grant database changes and Golden VM packaging are staged as separate linked PRs so this upstream-first desktop/relay diff stays reviewable.

Tracks #36 and parent #1.

cameronhotchkies and others added 30 commits July 25, 2026 08:07
## Why
The Codex adapter install-plan test depended on the process-global
login-shell PATH cache, making it flaky under concurrent or loaded CI
runs.

## What
- Thread an explicit probe PATH through adapter install planning
- Use a controlled PATH in Codex install-plan tests
- Update the existing desktop file-size allowance for the focused seam

## Risk Assessment
Low — production behavior keeps using the same augmented PATH; only
dependency injection and deterministic tests change.

## References
- Failure:
https://github.com/block/buzz/actions/runs/30110680608/job/89539202266
- Validation: `just desktop-tauri-test`; `cd desktop && pnpm check`;
full pre-push hooks

Generated with Codex

---------

Signed-off-by: npub1x4hk035p3p9q39a3fcrd2fe30lpkrhr5dwe0cqzzjphxyyh8m0gsq4vqap <356f67c681884a0897b14e06d527317fc361dc746bb2fc0042906e6212e7dbd1@buzz.block.builderlab.xyz>
Co-authored-by: npub1x4hk035p3p9q39a3fcrd2fe30lpkrhr5dwe0cqzzjphxyyh8m0gsq4vqap <356f67c681884a0897b14e06d527317fc361dc746bb2fc0042906e6212e7dbd1@buzz.block.builderlab.xyz>
Co-authored-by: Codex <noreply@openai.com>
…_COMMUNITIES_PER_OWNER (block#2599)

Closes block#2600

## Summary

Self-hosted multi-tenant deployments (one relay serving many communities
via host-based tenancy) routinely need more than three communities owned
by the same operator identity. `MAX_COMMUNITIES_PER_OWNER` is currently
a hardcoded const, and hitting it surfaces as a `limit_reached` 409 from
`POST /operator/communities` — which provisioning UIs tend to mislabel
(mine reported it as "subdomain already taken").

This makes the limit configurable per deployment:

- New env var `BUZZ_MAX_COMMUNITIES_PER_OWNER` — read once per process,
must parse as a positive integer; missing/invalid/non-positive values
fall back to the existing default of **3**, so current deployments are
unaffected.
- Enforcement locations are unchanged and stay in the authoritative
relay-layer checks: community provisioning
(`create_community_with_owner`) and ownership transfer (inside the
advisory-lock transaction).
- Parse/fallback rules are extracted into a pure helper
(`effective_owner_limit`) with unit tests, keeping the cached getter
trivial.

## Test plan

- `cargo test -p buzz-db --lib` — new `owner_limit_*` tests cover
default, invalid, non-positive, and positive-override cases.
(Pre-existing unrelated failure on clean main:
`replica_fence::tests::fence_starts_closed_and_opens_on_advance`,
tracked in block#2369.)
- `cargo clippy -p buzz-db --all-targets` and `cargo fmt` clean.
- Deployed on my multi-tenant relay (vibecode.casa) with
`BUZZ_MAX_COMMUNITIES_PER_OWNER=100`: provisioning a 4th community for
the same owner succeeds; without the var the stock limit of 3 still
applies.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Lee Salminen <leesalminen@gmail.com>
…ary (block#2842)

### What changed?

Made the mobile new-message directory providers
(`relayDirectoryUsersProvider`, `relayDirectorySearchProvider`)
`autoDispose`, and both now watch `relayConfigProvider` so they refetch
when the active relay/community configuration changes.

### Why?

Follow-up to block#2810 (Codex P1 review flag: "Invalidate the directory when
the community changes").

Both providers previously cached results for the whole app session. They
watched only the relay-session notifier (a stable instance that survives
dependency rebuilds) and the current pubkey, which keeps its value when
two communities share a signing key. Switching between such communities
could reopen the New message sheet showing the previous relay's people,
and submit their pubkeys to the current relay. The search provider was
also a non-autoDispose family keyed by raw query strings, so every
distinct typed query leaked a cached provider entry for the session.

Watching `relayConfigProvider` (which rebuilds on every community switch
via `activeCommunityProvider`) invalidates cached browse and search
results at the community boundary, and `autoDispose` releases the cache
when the sheet closes.

### How is it tested?

Full mobile suite green (585 passed / 1 skipped), `flutter analyze`
clean.

Added tests:

-
[`channel_management_provider_test.dart`](https://github.com/block/buzz/blob/gated/directory-provider-invalidation/mobile/test/features/channels/channel_management_provider_test.dart)
— browse and search refetch on relay-config change with an unchanged
session notifier and pubkey; cached search families are released once
unlistened.

Signed-off-by: npub1kqarnt4re38nuttqnml3mrqp8cnm6wzpywl2kesc2ejasp0luc5q275nkx <b03a39aea3cc4f3e2d609eff1d8c013e27bd384123beab66185665d805ffe628@buzz.block.builderlab.xyz>
Co-authored-by: npub1kqarnt4re38nuttqnml3mrqp8cnm6wzpywl2kesc2ejasp0luc5q275nkx <b03a39aea3cc4f3e2d609eff1d8c013e27bd384123beab66185665d805ffe628@buzz.block.builderlab.xyz>
…ership (block#2840)

## Problem

A fresh `deploy/compose` install never starts. The relay exits during
config
validation and crash-loops under `restart: unless-stopped`:

```
Error: Configuration error: invalid config: BUZZ_GIT_PACK_CACHE_PATH=/data/git/.pack-cache could not be created: Permission denied (os error 13)
```

## Cause

The image runs as `buzz:buzz`. Docker seeds a volume's ownership from
the image
**only when the mount point already exists there** — otherwise it
creates the
mount point as `root:root`. `compose.yml` mounts `buzz-git-data` at
`/data/git`,
which the image doesn't create, so the relay can't write the pack cache.

`USER buzz:buzz` and `BUZZ_GIT_REPO_PATH=/data/git` arrived in the same
commit
(426497a), so there was never a working state to regress from.

The other deployment paths are unaffected, which is probably why this
went
unnoticed: the code default (`./repos`, under the `buzz`-owned home)
works
unmounted, and the Helm chart gets volume ownership from `fsGroup:
65532`.

## Fix

Create `/data/git` as `buzz:buzz` in the image, the way the official
`postgres`
image prepares `$PGDATA`.

`compose.yml`, `run.sh` and the volume layout are untouched, so existing
deployments need no migration and the `run.sh backup-hint` output stays
accurate.

## Testing

`ghcr.io/block/buzz@sha256:ec96e029` on arm64 with this change applied
as a
derived layer, running **unmodified** `deploy/compose/compose.yml`
against clean
volumes:

| Image | `/data/git` owner | Result |
|---|---|---|
| upstream | `root:root` | `Permission denied` — crash loop |
| patched | `buzz:buzz` | relay healthy |

```
relay health: healthy
/data/git = buzz:buzz     (contains .pack-cache)
/_readiness -> 200
WS upgrade  -> 101
```

Relay log shows migrations applied, the A3 git object-store conformance
probe
passing, and `buzz-relay TCP listening`.

I have not run `just ci` / `just test` — this touches no Rust, JS, or
mobile
code. Happy to run them if you'd like.

## Alternatives

- A `git-init` one-shot in `compose.yml` that chowns the volume. Works,
but adds
a container to every `up` and doesn't help `docker run` or Kubernetes
without
  an `fsGroup`.
- Moving `BUZZ_GIT_REPO_PATH` to the code default `/var/lib/buzz/repos`
and
pre-creating that instead. Arguably tidier, since `/data/git` then only
lives
in the compose bundle — but it moves the volume mount point, so existing
installs would have to relocate their git data. Happy to do it that way
if you
  prefer; it seemed the wrong trade for a bug fix.

Signed-off-by: Onno Klein Hofmeijer <onnokh@hotmail.com>
…rning toast (block#2279)

Fixes block#1822

## Problem

Renaming an agent via the agent settings dialog performs a local save
(always succeeds) and a best-effort relay kind:0 republish (can fail:
network blip, auth expiry, relay unreachable). On sync failure the
dialog `console.warn`'d the error and closed as a clean success — the
user had no signal that the relay still holds the old name, which breaks
`@mention` resolution and shows the stale name in other agents' `From:`
lines (discovered via block#1743).

## Fix

Surface `profileSyncError` as a `toast.warning` in
`AgentInstanceEditDialog`, matching the treatment the create and
persona-save paths already give the same field
(`useManagedAgentActions.ts`, `UserProfilePanelPersonaSubmit.ts`). The
save is not blocked — the local rename is valid and persists, per the
issue's guidance.

The toast points at the retry path that actually works: **restarting the
agent**. Re-saving the same name does not retry — `update_managed_agent`
computes `name_changed` against the already-updated record, so a second
identical save skips the sync — but `start_managed_agent` fires
`reconcile_agent_profile`, which queries the relay's kind:0 and
republishes when the display name diverges.

Scope notes:

- `EditRespondToDialog` (the third caller of the update mutation) never
changes the name, and the Rust side only sets `profile_sync_error` when
the name changed — so no change needed there.
- The alternative fix in the issue (retry-with-backoff in
`sync_managed_agent_profile`) is not taken here;
`reconcile_agent_profile` on agent start already provides self-healing,
and this change makes that path discoverable at the moment of failure.

## Testing

- `just desktop-check` — biome + file-size + px-text + pubkey-truncation
guards clean.
- `just desktop-test` — 3331 passed, 0 failed.
- The handler branch is a straight conditional on the mutation result;
the repo's `.test.mjs` convention covers extracted pure logic, and there
is no extracted logic here to unit-test (consistent with the equivalent
toast branches on the create/persona paths).

---------

Signed-off-by: ayobamiseun <adegokeayobamiseun@gmail.com>
Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
…#2803)

When Desktop stops or restarts a managed-agent pair, the harness is
SIGKILLed after 1s and `turn_completed` never lands.
`activeAgentTurnsStore` then sees the "all turns silent at once" pattern
and delays badge cleanup for up to 3 min (the pause that protects live
badges during transient relay-stream gaps). This is unnecessary when
Desktop itself issued the kill — there is no relay-gap ambiguity.

## What changed

**`activeAgentTurnsStore.ts`** — new `clearActiveTurnsForAgent(pubkey)`
- Tombstones every live turn for the agent via `recordTerminal` (blocks
in-flight `turn_liveness` frames from resurrecting them via
`resurrectTurn`)
- Removes the agent's entry from `activeTurnsByAgent`
- Preserves `lastProcessed` (watermark) — full-buffer replay after the
clear is a no-op
- Preserves `clockOffsetByAgent` — still valid, harmless

**`managedAgentRuntimeHooks.ts`** — clearing at the successful-stop
boundary
- New `clearActiveTurnsForAgentOnStop(pubkey, relayUrl?)` — relay-scope
gate: only clears when the stopped pair's relay matches the active
community (pair-scoped), or when an active community is configured
(agent-wide ops)
- New `restartManagedAgentPair(pubkey, relayUrl, stop, clear, start)` —
dependency-injected stop → clear → start sequence; the `restart` branch
of `useManagedAgentRuntimeAction`'s `mutationFn` is a single call into
it. The clear fires after a successful stop and before start begins, so
the badge is gone even when start fails, a failed stop clears nothing,
and no clear can run after the new process is spawned (genuinely-new
turns are never wiped)
- `useManagedAgentRuntimeAction.onSuccess` clears for `stop` actions,
before the query-cache update

**`managedAgentControlActions.ts`** — `onStopped` callback on
`respawnManagedAgentWithRules`, invoked after the stop promise resolves
and before start begins

**`welcomeKickoff.ts`** — same `onStopped` boundary on
`restartWelcomeTeammate`

**Call sites covered (all stop/restart UI paths):**
- `useManagedAgentRuntimeAction` — pair-scoped stop (`onSuccess`) and
restart (`restartManagedAgentPair` in `mutationFn`); Members-sidebar +
settings card
- `useMembersSidebarActions.handleRespawnAll` — via `onStopped`
- `useMembersSidebarActions.handleStopAll` — direct local-stop branch
- `useMembersSidebarActions.handleLifecycleAction` — local-stop fallback
branch
- `useAgentLifecycleActions.handleAgentPrimaryAction` — Agents-tab stop
- `useAgentLifecycleActions.handleAgentRestart` — via `onStopped`
- `useManagedAgentActions.handleStop` / `handleBulkStopRunning` — Agents
screen
- `useAutoRestartPolicy` — inline, between stop and start
- `restartWelcomeTeammate` call site — via `onStopped`

Provider agents are excluded at each site: they go through `!shutdown`
(relay message), not a direct harness kill.

## Tests

Twelve behavior tests across three files:

- `activeAgentTurnsStore.test.mjs` (6) — clear removes the agent's turns
and notifies subscribers, other agents untouched; full-buffer replay
after clear is a no-op (watermark preserved); late `turn_liveness` frame
with timestamp ≤ clear time does not resurrect (tombstone); new
`turn_started` after clear is tracked normally; badge gone when stop
succeeds even if start fails; new frame during start-pending does not
resurrect the cleared badge
- `managedAgentControlActions.test.mjs` (3) — `onStopped` fires on
stop-success/start-failure; does not fire on stop-failure; strict stop →
`onStopped` → start ordering
- `managedAgentRuntimeHooks.test.mjs` (3) — pair-restart seam: clear ran
when start fails (rejection propagates); stop failure invokes neither
clear nor start; strict stop → clear → start ordering

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
## Why
Prepare the relay reconnect controller for an isolated behavior-fix PR
without changing runtime behavior in this one.

## What
- Export the current reconnect timing policy from
`RelayReconnectController`
- Allow controller tests to inject the complete timing policy
- Add characterization coverage for the production timing values and
injected fast-path/poll/backstop timers

## Risk Assessment
Low — this preserves the existing production timing values and only
replaces private module constants with a default policy object used by
the controller. The weak reconnect-timer and backstop wrapper
extractions were removed from this PR.

## References
- `cd desktop && pnpm typecheck`
- `cd desktop && pnpm check`
- `cd desktop && pnpm test` (3373 pass)
- `git diff --check`
- Push hooks were bypassed after the requested desktop validation
because the broad pre-push hook runs without Hermit here and fails on
Node 20/pnpm 11 plus unhealthy local Postgres services.

Generated with Codex

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: npub1m0vvn9qm5md0a080p27qzkm9uaw49e699ukwfq7fc0756xq0y5zqhzhdk2 <dbd8c9941ba6dafebcef0abc015b65e75d52e7452f2ce483c9c3fd4d180f2504@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@sprout-oss.stage.blox.sqprod.co>
## Why
Manual relay recovery polls every three seconds, repeatedly consuming
the session's exponential-backoff timer and leaving reconnect behavior
stuck or noisy on degraded networks.

## What
- Replace fixed-cadence Phase 3 polling with observation of the
RelayClient background reconnect loop
- Raise the fast-path deadline above the native websocket timeout and
enforce that contract in a regression test
- Keep the existing 120-second backstop as a soft UI timeout without
stopping background retries

## Risk Assessment
Medium — this changes live relay recovery timing, but removes a
competing retry loop rather than adding one. The existing
connection-state subscription remains the success signal.

## References
- Stacked on block#2310 (`lazyjoe/reconnect-testability-refactor`)
- Investigation: `RESEARCH/BUG_RELAY_RECONNECT_HANG.md`
- `just ci`
- `cd desktop && pnpm typecheck && pnpm check && pnpm test` (3485 pass)
- `git diff --check`

Generated with Codex

Signed-off-by: npub1x4hk035p3p9q39a3fcrd2fe30lpkrhr5dwe0cqzzjphxyyh8m0gsq4vqap <356f67c681884a0897b14e06d527317fc361dc746bb2fc0042906e6212e7dbd1@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1x4hk035p3p9q39a3fcrd2fe30lpkrhr5dwe0cqzzjphxyyh8m0gsq4vqap <356f67c681884a0897b14e06d527317fc361dc746bb2fc0042906e6212e7dbd1@sprout-oss.stage.blox.sqprod.co>
…#2780)

Replace the 5-file, ~180-line template surface with a 4-file, ~54-line
goose-modeled set.

## What changed

**`.github/PULL_REQUEST_TEMPLATE.md`** — rewritten to 8 lines: Summary,
Related issue (with inline duplicate-check prompt), Testing. Checklist
and AI-disclosure section removed.

**`.github/ISSUE_TEMPLATE/bug-report.yml` → `bug-report.md`** — replaced
YAML form with plain-markdown template (goose-style frontmatter).
Fields: describe the bug, repro steps, expected behavior, version + OS,
logs/context. Version guidance retained: Settings sidebar footer,
"unknown" accepted.

**`.github/ISSUE_TEMPLATE/feature-request.yml` → `feature-request.md`**
— replaced YAML form with plain-markdown template. Fields: motivation,
proposed solution, alternatives, additional context. Duplicate-check
line at the bottom (goose-style).

**`.github/ISSUE_TEMPLATE/question.yml`** — deleted. `config.yml`
updated to `blank_issues_enabled: true` so questions have somewhere to
go.

**`CONTRIBUTING.md`** — "Before You Open a PR" compressed to four prose
sentences: duplicate search, issue-first recommendation, AI ownership
(absorbs the dropped PR-template field), review cadence. Intro link
updated from the removed question form to plain `/issues/new`.

## Related issue
none found

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
…lock#2882)

Desktop's active-turn store capped tracked concurrent turns per agent at
`4` while the harness runs `DEFAULT_AGENT_PARALLELISM = 24` parallel
agent subprocesses and accepts up to `32` (`--agents` /
`BUZZ_ACP_AGENTS`, `value_parser range(1..=32)`). Turns above the cap
were silently evicted, so a genuinely-running turn lost its working
badge in the sidebar and the agents popover.

The eviction also caused the badge set to rotate indefinitely. Evicted
turns are still alive, so their hosts keep emitting `turn_liveness`
every 10s; `recordActivity` can't find the evicted turn, `resurrectTurn`
recreates it, and that eviction drops one of the surviving turns. With
two live turns above the cap the visible set churned every 10 seconds.

`MAX_TURNS_PER_AGENT` exists only to bound map growth, so it now sits at
the harness's hard upper bound of `32` — unreachable for any
legitimately-configured agent while still keeping the per-agent map
bounded. `MAX_TERMINAL_TOMBSTONES` derives from it (`* 4`), so the
tombstone cap moves from 16 to 128.

Two regressions cover the reported symptom: a default-parallelism agent
working in 24 channels keeps all 24 badges, and the tracked channel set
stays stable as `turn_liveness` arrives for turns that previously would
have been evicted.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
## Summary

- make Virtua the sole prepend geometry/correction owner by accumulating
active prepend ResizeObserver corrections from the live DOM offset
- retire prepend reconciliation on ordinary reader wheel input, while
preserving it for Ctrl+wheel browser zoom
- remove Buzz's competing three-second semantic-anchor watcher and
corrective `scrollBy` loop
- keep ESM/CJS Virtua patch behavior equivalent and update the patch
lock hash

## Why

Buzz admitted prepended rows using seeded estimates, then Virtua
received multiple measurement corrections for the same transaction. Each
correction was based on the same stale model offset, so a later write
replaced an earlier correction instead of accumulating it. In the
reproduced first page, that resurrected 452px of anchor drift; the
app-level watcher merely corrected the lost virtualizer write afterward.

This fixes the correction inside Virtua and deletes the competing app
writer, following the single-owner geometry invariant used by Berd
rather than copying its spacer implementation.

## Validation

- watcher-off desktop virtualization matrix: 11/11 passed, including 15
cascading prepends, continued wheel input, detached rich-row growth,
channel switching, bottom follow, and buffered live arrivals
- focused cascading prepend/Ctrl+wheel regression passed
- desktop typecheck passed
- desktop unit suite passed: 3,495 tests
- Biome passed on changed desktop files
- `git diff --check` clean

## Manual behavior

Load older history repeatedly while scrolling upward, then wheel
downward during/after a prepend. The visible anchor should stay within
the existing 5px contract during reconciliation, and deliberate reader
movement should not be pulled back. Ctrl+wheel during the prepend commit
must not cancel reconciliation.

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
## Summary

- retire Virtua prepend reconciliation on every ordinary reader wheel
event, including events suppressed from its separate wheel-timing
heuristic
- keep Ctrl+wheel browser zoom excluded from reader ownership
- arm the Ctrl+wheel prepend probe before pagination begins so it cannot
miss the commit
- preserve ESM/CJS patch parity and update the patch lock hash

## Why main was red

PR block#2855 added the reader-wheel retirement action after Virtua's
existing suppression guard. Once the first event set that guard, later
events in the same wheel burst returned before retiring prepend mode. A
late ResizeObserver correction could then pull the viewport backward by
20–40px. The same test had failed twice on block#2855 but passed its final
retry, so the PR job appeared green; the merge commit lost all three
retries.

The separate Ctrl+wheel failure was a test race: its MutationObserver
was registered after the request had already been triggered and could
miss the prepend commit.

## Validation

- desktop full unit suite: 3,515 passed
- pre-push desktop check: passed
- `git diff --check`: passed
- CI is the E2E verification; no local E2E was run

Fixes the main-branch failure in CI run 30180099007.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
…st (block#2890)

## Problem

The Databricks model dropdown offers a handful of stale models — and
there's no way to tell that list apart from the real one. The AI Gateway
exposes **66** chat/embedding endpoints on `block-lakehouse-production`,
but the picker was showing a short list that includes models the gateway
no longer serves and embedding endpoints that can't chat at all.

Three independent defects, all on the discovery path:

**1. Live discovery never ran for agents with no saved provider.**
`get_agent_models` gates every in-process discovery attempt on the
provider (`is_openai_compatible_provider` / `is_anthropic_provider` /
`is_databricks_provider`), reading it straight from `record.provider`.
That field is `null` for every agent record created before provider
persistence — and for any agent that inherits its provider from the
build. So all three gates saw `None`, no HTTP discovery ran, and the
request fell through to the `buzz-acp models` subprocess. On the
Databricks path that subprocess returns `discovery_failure_fallback` —
the small hardcoded `DATABRICKS_V2_KNOWN_MODELS` catalog — which the
frontend renders exactly like a live catalog. An internal DMG that bakes
`BUZZ_AGENT_PROVIDER=databricks_v2` and a `DATABRICKS_HOST` still got
the fallback.

**2. The fallback list couldn't represent the running model.**
When discovery genuinely fails, the picker should at minimum be able to
show what the agent is actually configured with. For `DatabricksV2` it
couldn't: the fallback returned only the hardcoded slate, so a model
like `databricks-gpt-5-5` wasn't selectable in its own picker.

**3. Embedding endpoints were offered as chat models.**
`databricks-bge-large-en` was selectable (visible in the dialog today).
The v2 endpoints payload carries no `task` or `state` field, so there is
nothing to filter on but the name.

## Changes

- **`effective_discovery_provider`** (new,
`desktop/src-tauri/src/commands/agent_models_env.rs`) — an explicit
provider (saved record value, or the create/edit dialog's current form
value) still always wins; when there is none, discovery falls back to
the runtime's own provider env var (`GOOSE_PROVIDER`,
`BUZZ_AGENT_PROVIDER`, …) read off the merged env, which by that point
already carries the baked build floor and the process env. Wired into
both `get_agent_models` and `discover_agent_models`.
`SavedAgentModelDiscoveryConfig` now carries `provider_env_var` from
`known_acp_runtime`, so each runtime reads *its own* key rather than a
shared guess.
- The relay-mesh branches in `discover_agent_models` deliberately keep
using `input.provider`: those key off a deliberate provider selection,
never a baked default.
- **Asserted vs inferred matters for missing credentials.** The OpenAI
and Anthropic gates error on a missing API key, while the Databricks
gate falls through; an inferred provider hitting the first two would
have replaced a working subprocess catalog with `config:
ANTHROPIC_API_KEY required` (`export GOOSE_PROVIDER=anthropic` is
goose's documented way to pick a provider, and it keeps the key in its
own keyring). So `effective_discovery_provider` returns a
`DiscoveryProvider` that remembers how the value was resolved, and
`required_env` only reports a missing credential for an asserted
provider. A wrong guess declines and lets the subprocess answer.
- **`is_chat_capable_endpoint`** (new,
`crates/buzz-agent/src/catalog.rs`) — applied in
`parse_v2_endpoints_page`. Drops `*embedding*` and segment-matched `bge`
/ `gte` endpoints; keeps everything unrecognised (fail-open, so a new
model family is never hidden). Segment matching is why it's `split('-')`
and not `contains`: a substring check would swallow legitimate names.
- **`discovery_failure_fallback`** for `Provider::DatabricksV2` now
leads with the configured model (deduped against the known slate,
blank-tolerant), so a failed discovery still yields a picker that can
show the running model. The configured model is trimmed once up front —
`resolve_model` doesn't trim, so a padded `DATABRICKS_MODEL` used to
slip past the dedupe and appear twice.
- **`sort_v2_endpoints_newest_first`** (new, second commit) — the
catalog is now ordered newest-first on each endpoint's
`created_timestamp`, ties broken by name. Previously Buzz sorted
nothing, so the gateway's own order reached the picker: it pages in two
phases (Databricks-managed, then workspace-created — the page token
decodes to `{"phase":"user"}`), each alphabetical, which buried
`databricks-claude-opus-5` 8th behind five older Claude endpoints and
`goose-claude-opus-5` — the newest endpoint in the catalog — 55th of 63.
Sorting in `fetch_v2_models` means both discovery paths inherit it with
no wire or type changes, and the combobox filter preserves incoming
order. Endpoints with an absent or unparseable timestamp sort last
rather than first, so a wire-shape change degrades to "unordered at the
bottom" instead of "shuffled to the top".
- The name tiebreak is load-bearing: eleven managed endpoints share one
placeholder timestamp (`1699610000000`), so without it their relative
order would vary between runs. That placeholder is also not always
accurate — a few genuinely recent endpoints
(`databricks-kimi-k2-7-code`, `databricks-llama-4-maverick`) land at the
bottom with the 2023 batch. The gateway offers nothing better to sort
on.
- Env/provider lookup helpers moved out of `agent_models.rs` into
`agent_models_env.rs`. This keeps the command module under the file-size
limit **without ratcheting the override up** — the existing 1079 entry
is untouched (file is now 1066 lines).

## Verification

Live against `block-lakehouse-production`, release build:

```
BUZZ_ACP_AGENT_COMMAND=$PWD/target/release/buzz-agent \
BUZZ_AGENT_PROVIDER=databricks_v2 \
DATABRICKS_HOST=https://block-lakehouse-production.cloud.databricks.com \
DATABRICKS_MODEL=databricks-gpt-5-5 \
./target/release/buzz-acp models --json
```

- before: 66 endpoints, including `databricks-bge-large-en`,
`databricks-gte-large-en`, `databricks-qwen3-embedding-0-6b`
- after: **63** endpoints, `[.models[] | select(.id |
test("embedding|-bge-|-gte-"))]` → `[]`

Top of the list after the sort commit:

```
goose-claude-opus-5                2026-07-24
databricks-claude-opus-5           2026-07-23
databricks-gemini-3-6-flash        2026-07-20
databricks-gemini-3-5-flash-lite   2026-07-20
databricks-inkling                 2026-07-14
```

Tests: 15 new (8 in `catalog.rs` — including the two-wire-shape
timestamp parse, the sort's tiebreak/no-timestamp cases, and the
padded-model dedupe — and 7 plus one assertion in
`agent_models_tests.rs`, 3 of them covering the asserted/inferred
credential split), two existing tests updated. `just check`, `just
test-unit`, and `just desktop-tauri-test` all pass (1636 desktop-tauri
tests, 274 buzz-agent lib tests).

Not run locally: the Docker-backed integration suite (`just test`) —
this diff touches neither `buzz-relay`, `buzz-db`, nor `buzz-auth`.

## Follow-ups (deliberately out of scope)

Two inference-path defects found while investigating, both reproduced
live against the gateway and both independent of discovery:

1. **Gemini thought signatures are dropped.** The gateway returns a bare
`thoughtSignature` on tool calls; the external-model serving endpoints
return it nested as `extra_content.google.thought_signature`. Neither
shape is round-tripped, so multi-turn tool use on `databricks-gemini-*`
fails with a 400 on the second turn.
2. **Array-shaped `content` is silently discarded.** Some models return
OpenAI `content` as a block array rather than a string; `parse_openai`'s
`str_field` returns `None` and the text is dropped.

The legacy `serving-endpoints` path does not work around either one, and
costs reasoning support on the GPT-5 family.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Refs block#2062

This carries forward the relay-mesh recovery work from block#2304 by @Bartok9
(cherry-picked with original authorship/sign-offs) and adds the
startup/readiness supervision and packaging fixes found while validating
it against a real two-machine Buzz setup.

## From block#2304

- Watch the local OpenAI ingress (`:9337`) after launch and re-arm a
stale relay-mesh runtime.
- Require consecutive failed probes before eviction so a transient
inference stall does not cause a cold restart.
- Bound stale-runtime shutdown, preserve runtime identity across the
asynchronous probe, and never evict a concurrent replacement.
- Re-arm only for agents that are actually running; deliberately stopped
agents stay stopped.
- Persist an actionable sentinel error under the managed-agent store
lock and clear only that error after recovery.
- Treat serve-to-client fallback as an intentional fail-safe; configured
serve restoration remains on its existing path.

## Added here

- Use the inference ingress (`:9337`), rather than management port
`:3131`, as Buzz's client-readiness boundary. A usable client no longer
fails or holds agent-save open merely because management startup is
still pending.
- Supervise the embedded SDK startup asynchronously, publish a pending
status while management is unavailable, and keep its mesh identity
alive.
- Avoid racing a replacement while SDK startup still owns the embedded
runtime. If that pending startup later loses ingress while a running
agent still needs it, request a controlled Buzz restart to reclaim the
otherwise-unreachable SDK thread.
- Defer roster-driven replacement while client management startup is
pending.
- Keep post-launch recovery in a dedicated module so the mesh entry
point remains within the desktop file-size gate.
- Explicitly mark generated Unix sidecars executable. On macOS, copying
over an existing non-executable destination preserved its old mode,
causing packaged `buzz-acp`, `buzz-agent`, and tool sidecars to be
reported as missing.

## Validation

Automated:

- `just ci` — passed, including formatting, Clippy with warnings denied,
desktop/web/mobile checks and tests, and builds.
- Full Tauri mesh-feature suite — 1,702 passed, 0 failed, 15 ignored.
- Mesh-feature Clippy with `-D warnings` — passed.
- Release macOS app bundle with `mesh-llm` — built successfully; every
bundled sidecar passed executable-mode and deep code-signature
verification.

Live two-machine E2E:

- M5: released Buzz serving `unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q4_K_M`.
- Mac mini: this branch's packaged Buzz running a saved
`buzz-agent`/relay-mesh agent.
- Confirmed `:9337` accepted inference and the saved ACP harness started
with no error while `:3131` was still unavailable.
- Exact inference succeeded before restart (`CORRECTED-MINI-E2E-OK`).
- Bespoke Buzz shut down cleanly in 2.3s, then restored ingress and the
saved harness in 18.8s while `:3131` was still unavailable.
- Exact inference succeeded after restart (`AFTER-RESTART-E2E-OK`).
- A real Buzz `@C55` message traversed desktop → `buzz-acp` →
`buzz-agent` → mini `:9337` → M5 compute and published the requested
reply successfully.

---------

Signed-off-by: Bartok9 <danielrpike9@gmail.com>
Signed-off-by: Michael Neale <michael.neale@gmail.com>
Co-authored-by: Bartok9 <danielrpike9@gmail.com>
Co-authored-by: Michael Neale <michael.neale@gmail.com>
…e Wayland clipboard (block#2904)

Fixes block#2896

**Root cause:** `arboard = "3"` compiles with default features only
(`image-data`), so the Linux build has no Wayland backend. In a Wayland
session every copy lands on the XWayland clipboard; the compositor only
mirrors X11 selections while an XWayland window holds focus, and
arboard's clipboard client is windowless, so Wayland-native apps read
nothing. `set_text` still returns `Ok`, which is why the invite dialog
shows "Copied" with an empty clipboard.

**Fix:** enable arboard's `wayland-data-control` feature. Under Wayland
it uses the `zwlr-data-control` protocol; when `WAYLAND_DISPLAY` is
absent it falls back to X11 as before. The `wl-clipboard-rs` dependency
is target-gated inside arboard, so macOS and Windows builds are
unchanged.

**Verified:** `cargo check` passes on the desktop crate; wl-clipboard-rs
now resolves in the lockfile.

Signed-off-by: Shawn Yeager <shawn@shawnyeager.com>
## Summary
- reorganize mobile settings around profile, appearance, and connection
cards
- add System/Light/Dark theme pairing, accent selection, and the Buzz
gradient theme
- align avatar badges, status editing, and supporting mobile chrome

## Test plan
- `just mobile-check`
- `just mobile-test`
(cherry picked from commit a3984c1)
@linear-code
linear-code Bot marked this pull request as ready for review July 26, 2026 12:34
@100yenadmin
100yenadmin marked this pull request as draft July 26, 2026 12:35
@100yenadmin
100yenadmin marked this pull request as ready for review July 26, 2026 14:26
@100yenadmin
100yenadmin merged commit 6d5c0af into main Jul 26, 2026
37 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

for (community_id, membership_pubkey) in manager.live_membership_principals() {
match check_member(community_id, membership_pubkey.clone()).await {

P1 Badge Bound the complete membership revalidation pass

Fresh evidence against the earlier delegated-agent tradeoff is that the advertised 15-second bound applies only to the timer interval: every live principal is checked sequentially here, with no per-query or whole-pass timeout. With enough principals, a saturated pool, or one slow database query, a removed principal later in the set can keep receiving community traffic far beyond 15 seconds after a missed Redis disconnect; batch or concurrently bound these checks so the complete pass—not merely its scheduling interval—meets the revocation limit.


getEvaosTeamsAuthStatus()
.then((status) => {
if (current) setManaged(status.managed);

P2 Badge Avoid refreshing auth just to choose the sign-out UI

Fresh evidence after the SettingsView fix is that opening Profile still mounts this independent entitlement refresh solely to determine whether the build is managed. If the remote refresh has a transient failure, the Rust command returns reauth_required and disables evaos_teams_authorized, while EvaosTeamsAuthGate retains its prior active state until its own timer fires—potentially an hour later—so the normal app stays visible while writes fail; derive this branch from desktopProductPolicy().managed instead.


let runtime = serde_json::to_vec(&(agent_command, agent_args))
.expect("serializing strings cannot fail");
Self {
relay_hash: hash_bytes(relay_url.as_bytes()),
agent_pubkey: agent_pubkey.to_ascii_lowercase(),
runtime_hash: hash_bytes(&runtime),

P2 Badge Include session-defining configuration in the scope hash

When an operator changes system_prompt, team instructions, or the base prompt but keeps the relay, agent key, command, and arguments unchanged, this scope hash remains identical. After restart the durable mapping therefore takes the session/load path and skips session/new, even though protocol-v2 system configuration is delivered only during session/new, leaving the channel on the old instructions indefinitely; hash the session-defining configuration or invalidate mappings when it changes.


: runtime.availability === "dependency_missing"
? " (setup needed)"

P1 Badge Update the scoped agent-config guide

This introduces a new rendered dependency_missing configuration state, but the scoped AGENTS.md is unchanged and the change contains no explicit “no rules changed” note. The repository requires every change to how agent configuration is modeled or rendered to update that guide in the same PR, so document the readiness/auth-state behavior and its acceptance gate before landing.

AGENTS.md reference: desktop/src/features/agents/AGENTS.md:L124-L131


export function evaosTeamsRefreshDelay(status: EvaosTeamsAuthStatus) {
const seconds = status.entitlement?.refreshAfterSeconds ?? 300;
return Math.min(Math.max(seconds, 30), 3600) * 1000;

P1 Badge Refresh no later than the entitlement expiry

A server response whose expiresAt is sooner than refreshAfterSeconds passes the Rust validation, but this timer uses only refreshAfterSeconds. For example, an entitlement expiring in one minute with a one-hour refresh interval leaves evaos_teams_authorized active for roughly 59 minutes after expiry, during which signing remains enabled; schedule using the minimum of the requested refresh and remaining lifetime, or reject an entitlement whose refresh point is not before expiry.


const status = await logoutEvaosTeams();
if (status.phase !== "signed_out") {
throw new Error(
status.message ?? "Hive could not confirm remote session revocation.",
);
}
window.location.reload();

P1 Badge Reset the active community when switching memberships

After managed sign-out, this reload preserves the previous active-community record in local storage. If the user then selects another membership whose entitlement points at a different relay, NativeBuzzApp remounts with the old relay and apply_workspace rejects it because managed workspaces must match the new entitlement; if both memberships share a relay, old community-scoped UI state can instead be presented under the new identity. Clear or replace the active managed community with the newly authenticated entitlement before remounting.


managed_store()
.replace_all(&existing)
.map_err(|_| "Hive cannot write to macOS Keychain".to_string())?;
if managed_store().load_all_readonly()? != Some(existing) {

P2 Badge Perform a real write in the Keychain writability probe

When the existing map is passed unchanged to replace_all, SecretStore::mutate_blob detects a no-op and deliberately skips write_blob_raw, so this function proves only that the Keychain is readable. In a read-allowed/write-denied Keychain state, Hive opens the browser and completes the full device flow before credential persistence finally fails; use a reversible probe value or a backend operation that actually exercises write permission before starting login.


*app_state
.relay_url_override
.lock()
.map_err(|error| error.to_string())? = Some(relay);

P1 Badge Route first-time setup through the entitled relay

Installing the entitlement writes the selected relay only to relay_url_override, but the no-community startup path calls get_default_relay_url, which still returns the static environment/build default and never reads this override. On a fresh Hive profile where that default differs from the server-selected relay, onboarding proposes the wrong community and the subsequent managed apply_workspace call rejects it, leaving the user unable to reach the entitled community; expose the active override to first-community initialization.


if state.config.require_relay_membership {
match state
.db
.is_relay_member(conn.tenant.community(), &membership_pubkey.to_hex())
.await

P1 Badge Avoid querying membership for every WebSocket event

On closed relays this performs a PostgreSQL lookup for every EVENT before even the pubkey, scope, kind, or signature checks. An authenticated socket can therefore stream malformed frames and turn each cheap rejection into a database query, while normal high-rate typing or observer traffic also adds one query per frame; this can exhaust the pool and stall the relay. Use a bounded positive-membership cache tied to the revalidator, or perform admission/rate checks before touching the database.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

This was referenced Jul 27, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.