Merge remote-tracking branch 'origin/main' into eva/card-mint-key-ux

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
This commit is contained in:
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d
2026-07-28 10:36:59 -04:00
co-authored by Tyler Longwell
15 changed files with 383 additions and 41 deletions
+8 -1
View File
@@ -34,6 +34,10 @@ REDIS_URL=redis://localhost:6379
# Max connections in the relay's shared Redis pool (default 16).
# BUZZ_REDIS_POOL_SIZE=16
# Max connections in each of the relay's Postgres pools — writer and, when
# READ_DATABASE_URL is set, reader (default 50).
# BUZZ_DB_POOL_SIZE=50
# -----------------------------------------------------------------------------
# Typesense (search)
# -----------------------------------------------------------------------------
@@ -105,7 +109,10 @@ RELAY_URL=ws://localhost:3000
# -----------------------------------------------------------------------------
# Logging / Tracing
# -----------------------------------------------------------------------------
RUST_LOG=buzz_relay=debug,buzz_db=debug,buzz_auth=debug,buzz_pubsub=debug,tower_http=debug
RUST_LOG=buzz_relay=debug,buzz_datastore=info,buzz_db=debug,buzz_auth=debug,buzz_pubsub=debug,tower_http=debug
# Optional OpenTelemetry-only target filter. This is deliberately independent
# from RUST_LOG so log verbosity changes cannot break trace parentage.
# BUZZ_OTEL_FILTER=buzz_relay=info,buzz_datastore=info
# OTLP tracing endpoint (optional — leave unset to disable)
# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
+56 -19
View File
@@ -1,6 +1,8 @@
name: Docker image
# Builds and publishes the public Buzz relay image as ghcr.io/block/buzz.
# Builds and publishes the public Buzz relay images as ghcr.io/block/buzz.
# Normal tags contain stripped binaries; matching debug-* tags contain the same
# optimized binaries with line-table debug information for native profilers.
#
# Strategy: each architecture builds on its native runner (ubuntu-24.04 for
# amd64, ubuntu-24.04-arm for arm64), pushes to GHCR by digest, then a final
@@ -15,8 +17,10 @@ name: Docker image
#
# Triggers:
# - push to main → :main + :sha-<7>
# + :debug-main + :debug-sha-<7>
# - push tags relay-v*.*.* → :{version} + :{major}.{minor} + :{major}
# (+ :latest for stable, NOT for prereleases)
# + matching :debug-* tags
# (+ :latest/:debug-latest for stable releases)
# - pull_request → build only (no push), cache stays warm
# - workflow_dispatch → manual relay-tag rescue at the tag itself
#
@@ -95,10 +99,6 @@ jobs:
runner: ubuntu-24.04-arm
arch: arm64
outputs:
# Used downstream by `merge` to stitch the manifest.
version: ${{ steps.meta.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
@@ -163,12 +163,13 @@ jobs:
org.opencontainers.image.description=WebSocket relay server for the Buzz communications platform
org.opencontainers.image.licenses=Apache-2.0
- name: Build and push by digest
id: build
- name: Build and push release image by digest
id: build-release
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: .
file: ./Dockerfile
target: runtime
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
# Push by digest, not by tag — the merge job assembles the tags
@@ -180,25 +181,49 @@ jobs:
cache-to: |
${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && format('type=registry,ref={0}-buildcache:{1},mode=max,compression=zstd', env.IMAGE_NAME, matrix.arch) || '' }}
- name: Export digest
- name: Build and push debug image by digest
id: build-debug
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: .
file: ./Dockerfile
target: runtime-debug
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }}
cache-from: |
type=registry,ref=${{ env.IMAGE_NAME }}-buildcache:${{ matrix.arch }}
- name: Export release and debug digests
if: github.event_name != 'pull_request'
env:
DIGEST: ${{ steps.build.outputs.digest }}
RELEASE_DIGEST: ${{ steps.build-release.outputs.digest }}
DEBUG_DIGEST: ${{ steps.build-debug.outputs.digest }}
run: |
mkdir -p /tmp/digests
touch "/tmp/digests/${DIGEST#sha256:}"
mkdir -p /tmp/digests-release /tmp/digests-debug
touch "/tmp/digests-release/${RELEASE_DIGEST#sha256:}"
touch "/tmp/digests-debug/${DEBUG_DIGEST#sha256:}"
- name: Upload digest
- name: Upload release digest
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: digests-${{ matrix.arch }}
path: /tmp/digests/*
name: digests-release-${{ matrix.arch }}
path: /tmp/digests-release/*
if-no-files-found: error
retention-days: 1
- name: Upload debug digest
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: digests-debug-${{ matrix.arch }}
path: /tmp/digests-debug/*
if-no-files-found: error
retention-days: 1
merge:
name: Merge multi-arch manifest
name: Merge ${{ matrix.variant }} multi-arch manifest
if: github.event_name != 'pull_request'
runs-on: ubuntu-24.04
needs: build
@@ -208,13 +233,21 @@ jobs:
packages: write # push the merged manifest
id-token: write # OIDC for provenance attestation on the manifest
attestations: write
strategy:
fail-fast: false
matrix:
include:
- variant: release
tag_prefix: ""
- variant: debug
tag_prefix: debug-
steps:
- name: Download all per-arch digests
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: /tmp/digests
pattern: digests-*
pattern: digests-${{ matrix.variant }}-*
merge-multiple: true
- name: Set up Docker Buildx
@@ -237,9 +270,12 @@ jobs:
# the build job's `meta` step for why match=^relay-v(.*)$, why
# value=${{ inputs.version }} carries the rescue-dispatch version,
# and why :latest is left to flavor.latest=auto.
flavor: |
latest=auto
prefix=${{ matrix.tag_prefix }},onlatest=true
tags: |
type=ref,event=branch,enable=${{ github.event_name != 'workflow_dispatch' || inputs.version == '' }}
type=sha,prefix=sha-,format=short,enable=${{ github.event_name != 'workflow_dispatch' || inputs.version == '' }}
type=sha,prefix=${{ matrix.tag_prefix }}sha-,format=short,enable=${{ github.event_name != 'workflow_dispatch' || inputs.version == '' }}
type=semver,pattern={{version}},match=^relay-v(.*)$,value=${{ inputs.version }}
type=semver,pattern={{major}}.{{minor}},match=^relay-v(.*)$,value=${{ inputs.version }}
type=semver,pattern={{major}},match=^relay-v(.*)$,value=${{ inputs.version }}
@@ -284,11 +320,12 @@ jobs:
- name: Summary
env:
IMAGE_NAME: ${{ env.IMAGE_NAME }}
VARIANT: ${{ matrix.variant }}
MERGED_DIGEST: ${{ steps.manifest.outputs.digest }}
META_TAGS: ${{ steps.meta.outputs.tags }}
run: |
{
echo "### Published \`${IMAGE_NAME}\`"
echo "### Published \`${IMAGE_NAME}\` (${VARIANT})"
echo
echo "**Digest:** \`${MERGED_DIGEST}\`"
echo
+54
View File
@@ -1,5 +1,59 @@
# Changelog
## v0.5.0
- feat(invites): add use-limited invite links ([#3141](https://github.com/block/buzz/pull/3141)) ([`d500c2d5c`](https://github.com/block/buzz/commit/d500c2d5cf5d9aabe0ca4ebebfcafdbe5f5b7fd3))
- fix(node): bump Buzz-supplied Node runtimes past OpenClaw's >=24.15.0 floor ([#3218](https://github.com/block/buzz/pull/3218)) ([`98a7b1334`](https://github.com/block/buzz/commit/98a7b1334823ee0be3e3fa5cab7a2e349e438dab))
- fix(desktop): preserve thread anchor through layout reflow ([#3212](https://github.com/block/buzz/pull/3212)) ([`9810d8545`](https://github.com/block/buzz/commit/9810d8545937329f229ff40d8a19edc9e3e325c1))
- feat(search): parse from:/in:/after:/before: and pass them in the filter ([#2871](https://github.com/block/buzz/pull/2871)) ([`cb2a265b5`](https://github.com/block/buzz/commit/cb2a265b5399426e808461c1a16713754c593258))
- fix(desktop): fetch join policies through native networking ([#2862](https://github.com/block/buzz/pull/2862)) ([`0019f8076`](https://github.com/block/buzz/commit/0019f80765e96f056e81b57789b8b5fb80936f72))
- fix(desktop): republish agent identity records when a persona rename propagates ([#2607](https://github.com/block/buzz/pull/2607)) ([`7ca0bbd94`](https://github.com/block/buzz/commit/7ca0bbd946fd82a7008132f94d069a97bb53f94b))
- fix(desktop): keep project Inbox previews compact ([#3193](https://github.com/block/buzz/pull/3193)) ([`de1396050`](https://github.com/block/buzz/commit/de13960505fd798070e177cb33b1663100ac06bb))
- Inbox refactor ([#2045](https://github.com/block/buzz/pull/2045)) ([`2bd4c24b7`](https://github.com/block/buzz/commit/2bd4c24b71335e7ce272ec6de6491f7f37f4b20d))
- Fix composer selection formatting and drop overlay ([#3172](https://github.com/block/buzz/pull/3172)) ([`99da5b7eb`](https://github.com/block/buzz/commit/99da5b7ebb19e26453e075bfb949672122b31be3))
- Refine pending message status ([#3153](https://github.com/block/buzz/pull/3153)) ([`75588eaff`](https://github.com/block/buzz/commit/75588eaff2354d620e554c055b80ec83735ddb0a))
- fix(desktop): recover full local storage on startup ([#3182](https://github.com/block/buzz/pull/3182)) ([`174c38e4b`](https://github.com/block/buzz/commit/174c38e4bd1ed8498641546bc4fcb6d5a4c9cede))
- fix(desktop): keep collapsed table separators out of spoilers ([#3169](https://github.com/block/buzz/pull/3169)) ([`4d8b676bb`](https://github.com/block/buzz/commit/4d8b676bb283a1917cec5850c3b7327fe122b0c1))
- feat(desktop): redesign agent runtime settings ([#3093](https://github.com/block/buzz/pull/3093)) ([`d98da7389`](https://github.com/block/buzz/commit/d98da7389e60cfbd79b219aa411449fe2e53a18a))
- fix(desktop): use forward slashes for git credential.helper on Windows ([#3023](https://github.com/block/buzz/pull/3023)) ([`899531684`](https://github.com/block/buzz/commit/8995316844f7ad50552fbae67fbd35119262796f))
- chore(desktop): add AgentCreationPreview file-size override to unblock main CI ([#3154](https://github.com/block/buzz/pull/3154)) ([`b92a1f4bf`](https://github.com/block/buzz/commit/b92a1f4bf400e7da5ab7a010cdd81a69497d8191))
- fix(desktop): make the test loader work on Windows ([#2758](https://github.com/block/buzz/pull/2758)) ([`8bb43d519`](https://github.com/block/buzz/commit/8bb43d51912894553f2670b2d285a96cf09cd472))
- fix(desktop): make lint and unit-test gates work on Windows ([#2943](https://github.com/block/buzz/pull/2943)) ([`545bb46b8`](https://github.com/block/buzz/commit/545bb46b824a3fbf4401062f03b72531d832ebb9))
- feat(desktop): add search to agent emoji picker ([#2630](https://github.com/block/buzz/pull/2630)) ([`313f793c8`](https://github.com/block/buzz/commit/313f793c8753d413c22ff8edfe420d5ee78708bc))
- fix(desktop): keep identity key help dialog readable in dark mode ([#2854](https://github.com/block/buzz/pull/2854)) ([`be275cfc6`](https://github.com/block/buzz/commit/be275cfc6c7b80fe43e9d66c6d14b6d2bbe58a10))
- feat(acp): title agent sessions from the agent and channel name ([#3028](https://github.com/block/buzz/pull/3028)) ([`f2fe3b63c`](https://github.com/block/buzz/commit/f2fe3b63c21be55907175715c076cd3a9195b74d))
- feat(git): use agent display name as git author name ([#3040](https://github.com/block/buzz/pull/3040)) ([`18eef633d`](https://github.com/block/buzz/commit/18eef633d88ac465c61d98f12655fbf51dc3ca44))
- fix(deps): bump nostr to 0.44.6 for RUSTSEC-2026-0216 (NIP-44 remote DoS) ([#3135](https://github.com/block/buzz/pull/3135)) ([`31e2de196`](https://github.com/block/buzz/commit/31e2de1966672e73e026af3c54f3a1a9a2f5e103))
- fix(desktop): read the newest pair-scoped harness log ([#3134](https://github.com/block/buzz/pull/3134)) ([`654f38490`](https://github.com/block/buzz/commit/654f384906b5c720a60a199d85031a6f1cb6efc9))
- feat(desktop): handle project work from Inbox ([#3117](https://github.com/block/buzz/pull/3117)) ([`c5c4f390b`](https://github.com/block/buzz/commit/c5c4f390b6713256e2efb8394c59823ebad73db6))
- fix(desktop): clarify identity key button when key exists ([#2357](https://github.com/block/buzz/pull/2357)) ([`87b3fcd3c`](https://github.com/block/buzz/commit/87b3fcd3c0131683569dd4268b099d18b25dcd5e))
- Restore Goose and Buzz Agent to onboarding harness selection ([#2731](https://github.com/block/buzz/pull/2731)) ([`7fc0cc82d`](https://github.com/block/buzz/commit/7fc0cc82db4d9dced9c258bbe8b530164a832a77))
- fix(desktop): render rich project work item content ([#3100](https://github.com/block/buzz/pull/3100)) ([`afb272bb7`](https://github.com/block/buzz/commit/afb272bb7b8d7d45d7de676fa97dcd5a8eefacc7))
- feat(acp): bring your own harness (BYOH) — generic ACP runtime seam + settings gallery ([#2773](https://github.com/block/buzz/pull/2773)) ([`95fdf9788`](https://github.com/block/buzz/commit/95fdf978800982389b120c66ff5e766d785419c7))
- feat(desktop): use collective mesh routing for Auto ([#2825](https://github.com/block/buzz/pull/2825)) ([`16d4ec335`](https://github.com/block/buzz/commit/16d4ec335e210295a9d9f77f36c1e85a18b6814a))
- fix(desktop): strip legacy baked team instructions from stored prompts ([#3035](https://github.com/block/buzz/pull/3035)) ([`aee631448`](https://github.com/block/buzz/commit/aee63144843854ee32ed9d36a2e7511c82ddc6b0))
- feat(agents): lower default agent parallelism from 24 to 10 ([#3038](https://github.com/block/buzz/pull/3038)) ([`5d8ede446`](https://github.com/block/buzz/commit/5d8ede446f8fdc48146fe56d389cab6bf3500f92))
- Polish community rail and mobile pairing ([#2972](https://github.com/block/buzz/pull/2972)) ([`e6c90bb7c`](https://github.com/block/buzz/commit/e6c90bb7c430d1b2af16508b634f9a5283b7fa3b))
- fix(desktop): remove bundled libsystemd from AppImage ([#2353](https://github.com/block/buzz/pull/2353)) ([`a31fc4d2f`](https://github.com/block/buzz/commit/a31fc4d2f35d51cdf45ff8c61fc3a07f49c665e8))
- fix(desktop): make agent definition authoritative for model/provider/prompt ([#1968](https://github.com/block/buzz/pull/1968)) ([`8c0e8cb16`](https://github.com/block/buzz/commit/8c0e8cb1656b04ad269bce3c2deeda2a943ae78a))
- chore(desktop): delete dead persona catalog UI cluster ([#2886](https://github.com/block/buzz/pull/2886)) ([`8e67cf399`](https://github.com/block/buzz/commit/8e67cf399d0291bcdbc69cd0402983ca030f05bb))
- fix(desktop): surface install failures hidden by curl-pipe exit codes ([#2892](https://github.com/block/buzz/pull/2892)) ([`166c6655e`](https://github.com/block/buzz/commit/166c6655e8bca87d83ad60c087fb70a32a026baf))
- Refactor managed-agent runtime into cohesive modules ([#2974](https://github.com/block/buzz/pull/2974)) ([`74b63e184`](https://github.com/block/buzz/commit/74b63e1846212af6e6751a62cfc631f74b1dfe07))
- fix(desktop): make Linux AppImage GStreamer work on non-Debian distros ([#2176](https://github.com/block/buzz/pull/2176)) ([`cc6c4d347`](https://github.com/block/buzz/commit/cc6c4d3471629fad018bcf645f9471a01b9ffe2f))
- refactor(desktop): remove Agent directory section from Agents page ([#2290](https://github.com/block/buzz/pull/2290)) ([`5d1233e84`](https://github.com/block/buzz/commit/5d1233e841b0efa91470bb45467b2c8e4284ebf6))
- fix(desktop): enable arboard Wayland backend so Linux copies reach the Wayland clipboard ([#2904](https://github.com/block/buzz/pull/2904)) ([`ab7aa8b12`](https://github.com/block/buzz/commit/ab7aa8b1200710dbc2d7a8661ed5aab95c4199c1))
- fix(desktop): supervise and re-arm relay-mesh runtime ([#2823](https://github.com/block/buzz/pull/2823)) ([`aa51dab9d`](https://github.com/block/buzz/commit/aa51dab9da5fef7054d03cf1a1207986d0000684))
- fix(agents): run live Databricks discovery instead of the fallback list ([#2890](https://github.com/block/buzz/pull/2890)) ([`8eb6e3eb6`](https://github.com/block/buzz/commit/8eb6e3eb601174249642373a6a367262fa476753))
- fix(desktop): retire prepend mode on every reader wheel ([#2913](https://github.com/block/buzz/pull/2913)) ([`07d0265cf`](https://github.com/block/buzz/commit/07d0265cfc212ef02e1c26153bf58ff46ce5ffe6))
- fix(desktop): consolidate prepend scroll correction ([#2855](https://github.com/block/buzz/pull/2855)) ([`25e7864b3`](https://github.com/block/buzz/commit/25e7864b35f4dfd1c0ff31304a38555230a85f8d))
- fix(desktop): track concurrent agent turns up to the harness maximum ([#2882](https://github.com/block/buzz/pull/2882)) ([`20bff5910`](https://github.com/block/buzz/commit/20bff591023daffc5ee1032cff02b54b75da3567))
- fix(relay): preserve reconnect backoff ([#2759](https://github.com/block/buzz/pull/2759)) ([`499c5d349`](https://github.com/block/buzz/commit/499c5d349dab13bc906b1af5fe1fcb09ce2afa81))
- refactor(relay): expose reconnect timing policy ([#2310](https://github.com/block/buzz/pull/2310)) ([`2f0041595`](https://github.com/block/buzz/commit/2f0041595d72529c06885680d2bd07ddb6a0beb4))
- fix(desktop): clear stale working badges on agent stop/restart ([#2803](https://github.com/block/buzz/pull/2803)) ([`a64cc71f6`](https://github.com/block/buzz/commit/a64cc71f6c1605279b1a6fbd0fe904a2984cbdb0))
- fix(desktop): surface agent rename relay profile sync failure as a warning toast ([#2279](https://github.com/block/buzz/pull/2279)) ([`5e3d2e484`](https://github.com/block/buzz/commit/5e3d2e4849c0f2512330801d804fb96f4ab72d28))
- fix(discovery): inject PATH into Codex adapter planning ([#2767](https://github.com/block/buzz/pull/2767)) ([`6ab3835f3`](https://github.com/block/buzz/commit/6ab3835f3fe89ee215819fe8d193463c0ae7472b))
## v0.4.26
- Style mobile pairing QR codes ([#2775](https://github.com/block/buzz/pull/2775)) ([`50655ac09`](https://github.com/block/buzz/commit/50655ac097fbf1a7db1a5284dccc7e2a0b0f1bfc))
+26 -7
View File
@@ -59,6 +59,9 @@ RUN apt-get update \
ca-certificates \
git \
&& rm -rf /var/lib/apt/lists/*
# Keep enough DWARF for native profilers to resolve optimized code to source
# locations. The normal runtime strips it below; runtime-debug retains it.
ENV CARGO_PROFILE_RELEASE_DEBUG=line-tables-only
COPY --from=planner /build/recipe.json recipe.json
# Cook the full workspace recipe — relay deps include workspace siblings, so
# scoping to -p buzz-relay misses transitive deps and re-builds them later.
@@ -66,8 +69,12 @@ RUN cargo chef cook --release --recipe-path recipe.json
COPY . .
RUN cargo build --release --locked -p buzz-relay --bin buzz-relay \
-p buzz-admin --bin buzz-admin \
-p buzz-pair-relay --bin buzz-pair-relay \
&& strip target/release/buzz-relay \
-p buzz-pair-relay --bin buzz-pair-relay
# Derive the normal release binaries from the same optimized ELF files as the
# debug image so the two variants cannot drift at code-generation time.
FROM builder AS stripped-binaries
RUN strip target/release/buzz-relay \
&& strip target/release/buzz-admin \
&& strip target/release/buzz-pair-relay
@@ -111,8 +118,8 @@ COPY web/ web/
COPY admin-web/ admin-web/
RUN pnpm -C web build && pnpm -C admin-web build
# ─── Stage 5: runtime ───────────────────────────────────────────────────────
FROM debian:${DEBIAN_VERSION}-slim AS runtime
# ─── Stage 5: shared runtime ────────────────────────────────────────────────
FROM debian:${DEBIAN_VERSION}-slim AS runtime-base
# OCI annotations: required for GHCR to auto-link the image to this repo and
# inherit its visibility. org.opencontainers.image.source is the load-bearing
@@ -135,9 +142,6 @@ RUN apt-get update \
&& useradd --system --uid 1000 --gid 1000 --home-dir /var/lib/buzz \
--create-home --shell /usr/sbin/nologin buzz
COPY --from=builder /build/target/release/buzz-relay /usr/local/bin/buzz-relay
COPY --from=builder /build/target/release/buzz-admin /usr/local/bin/buzz-admin
COPY --from=builder /build/target/release/buzz-pair-relay /usr/local/bin/buzz-pair-relay
COPY --from=web-builder /build/web/dist /srv/buzz/web
COPY --from=web-builder /build/admin-web/dist /srv/buzz/admin-web
@@ -157,3 +161,18 @@ USER buzz:buzz
WORKDIR /var/lib/buzz
ENTRYPOINT ["/usr/local/bin/buzz-relay"]
# Optimized binaries with line-table debug information for native profiling.
# Published under debug-* tags; runtime behavior otherwise matches the normal
# image exactly.
FROM runtime-base AS runtime-debug
COPY --from=builder /build/target/release/buzz-relay /usr/local/bin/buzz-relay
COPY --from=builder /build/target/release/buzz-admin /usr/local/bin/buzz-admin
COPY --from=builder /build/target/release/buzz-pair-relay /usr/local/bin/buzz-pair-relay
# Keep the stripped runtime as the final/default Dockerfile target so existing
# `docker build .` callers and release tags retain their current behavior.
FROM runtime-base AS runtime
COPY --from=stripped-binaries /build/target/release/buzz-relay /usr/local/bin/buzz-relay
COPY --from=stripped-binaries /build/target/release/buzz-admin /usr/local/bin/buzz-admin
COPY --from=stripped-binaries /build/target/release/buzz-pair-relay /usr/local/bin/buzz-pair-relay
+5 -2
View File
@@ -57,10 +57,13 @@ or mobile GitHub Release.
2. **Merge the PR.** `auto-tag-on-release-pr-merge` pushes
`relay-v<version>`.
3. **The tag triggers `docker.yml`.** Stable releases update the version
aliases and `latest`; prereleases do not.
aliases and `latest`; prereleases do not. Each release also publishes an
optimized, symbol-bearing image under matching `debug-` tags (for example,
`debug-0.3.0` and `debug-latest`) for native profiling. The ordinary tags
remain stripped and are the default for deployments that do not need it.
Every push to `main` continues to publish the rolling relay `:main` and
`:sha-<7>` tags.
`:sha-<7>` tags, plus matching `:debug-main` and `:debug-sha-<7>` variants.
### Mobile
+40 -2
View File
@@ -1854,7 +1854,8 @@ pub enum ModelSwitchMethod {
/// Extract `configOptions` entries with `category == "model"` from a `session/new` result.
///
/// Returns the raw JSON array entries. Each entry has `configId`, `displayName`,
/// Returns the raw JSON array entries. Each entry has `configId` (spelled `id`
/// by some adapters, e.g. claude-agent-acp), `displayName`,
/// `options: [{ value, displayName }]`, etc.
pub fn extract_model_config_options(result: &serde_json::Value) -> Vec<serde_json::Value> {
result["configOptions"]
@@ -1888,7 +1889,14 @@ pub fn resolve_model_switch_method(
// 1. Search stable configOptions for a "model"-category entry whose
// options contain a value matching desired_model.
for config_opt in extract_model_config_options(session_new_result) {
let config_id = match config_opt.get("configId").and_then(|v| v.as_str()) {
// Adapters disagree on the key: the ACP spec says `configId`, but
// claude-agent-acp emits `id`. Accept both; the set request always
// uses `configId` on the wire.
let config_id = match config_opt
.get("configId")
.or_else(|| config_opt.get("id"))
.and_then(|v| v.as_str())
{
Some(id) => id,
None => continue,
};
@@ -2464,6 +2472,36 @@ mod tests {
);
}
#[test]
fn resolve_accepts_id_keyed_config_options() {
// claude-agent-acp (observed on v0.61.0) keys config options with
// `id` instead of the spec's `configId`. Payload mirrors its real
// `session/new` response.
let result = serde_json::json!({
"configOptions": [{
"id": "model",
"name": "Model",
"category": "model",
"type": "select",
"currentValue": "default",
"options": [
{ "value": "default", "name": "Default" },
{ "value": "opus[1m]", "name": "Opus" },
{ "value": "sonnet", "name": "Sonnet" }
]
}],
"models": null
});
let method = super::resolve_model_switch_method(&result, "opus[1m]");
assert_eq!(
method,
Some(super::ModelSwitchMethod::ConfigOption {
config_id: "model".to_string(),
option_value: "opus[1m]".to_string(),
})
);
}
#[test]
fn resolve_falls_back_to_unstable() {
let result = serde_json::json!({
+1
View File
@@ -92,3 +92,4 @@ reqwest = { workspace = true }
tokio-tungstenite = { workspace = true }
futures = "0.3"
flate2 = "1.1.9"
opentelemetry_sdk = { workspace = true, features = ["testing"] }
+41
View File
@@ -64,6 +64,14 @@ pub struct Config {
/// pod is only 4 — small enough that rate-limit checks, presence, and
/// pub/sub publishes queue behind each other under load.
pub redis_pool_size: usize,
/// Maximum connections in the Postgres writer/reader pools. Defaults to 50.
///
/// The `buzz-db` default of 20 was sized for a handful of pods against
/// `max_connections=100`. Against Aurora (~5,000 connections) that cap
/// is the binding constraint: a burst of concurrent handlers exhausts
/// the per-pod pool and requests fail on acquire timeout while the
/// database sits idle.
pub db_pool_size: u32,
/// Public WebSocket URL of this relay, advertised in NIP-11.
pub relay_url: String,
/// Public WebSocket URL of the dedicated device-pairing relay, when configured.
@@ -424,6 +432,12 @@ impl Config {
.filter(|&v| v > 0)
.unwrap_or(16);
let db_pool_size = std::env::var("BUZZ_DB_POOL_SIZE")
.ok()
.and_then(|v| v.parse::<u32>().ok())
.filter(|&v| v > 0)
.unwrap_or(50);
let relay_url =
std::env::var("RELAY_URL").unwrap_or_else(|_| "ws://localhost:3000".to_string());
@@ -875,6 +889,7 @@ impl Config {
read_database_url,
redis_url,
redis_pool_size,
db_pool_size,
relay_url,
pairing_relay_url,
max_connections,
@@ -942,6 +957,7 @@ mod tests {
assert!(!config.database_url.is_empty());
assert!(!config.redis_url.is_empty());
assert_eq!(config.redis_pool_size, 16);
assert_eq!(config.db_pool_size, 50);
assert!(config.max_connections > 0);
assert!(config.send_buffer_size > 0);
assert_eq!(config.max_frame_bytes, DEFAULT_MAX_FRAME_BYTES);
@@ -1009,6 +1025,31 @@ mod tests {
assert_eq!(junk, 16, "unparsable value must fall back to the default");
}
#[test]
fn db_pool_size_env_override_and_invalid_fallback() {
let _guard = ENV_MUTEX.lock().unwrap();
let previous = std::env::var_os("BUZZ_DB_POOL_SIZE");
std::env::set_var("BUZZ_DB_POOL_SIZE", "80");
let overridden = Config::from_env().expect("config").db_pool_size;
std::env::set_var("BUZZ_DB_POOL_SIZE", "0");
let zero = Config::from_env().expect("config").db_pool_size;
std::env::set_var("BUZZ_DB_POOL_SIZE", "not-a-number");
let junk = Config::from_env().expect("config").db_pool_size;
if let Some(value) = previous {
std::env::set_var("BUZZ_DB_POOL_SIZE", value);
} else {
std::env::remove_var("BUZZ_DB_POOL_SIZE");
}
assert_eq!(overridden, 80);
assert_eq!(zero, 50, "zero must fall back to the default");
assert_eq!(junk, 50, "unparsable value must fall back to the default");
}
#[test]
fn read_database_url_unset_or_blank_is_none() {
let _guard = ENV_MUTEX.lock().unwrap();
+62 -3
View File
@@ -4,6 +4,10 @@ use std::sync::Arc;
use tracing::{error, info, warn};
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
fn log_env_filter(rust_log: Option<&str>) -> EnvFilter {
EnvFilter::new(rust_log.unwrap_or("buzz_relay=info"))
}
use uuid::Uuid;
use buzz_audit::AuditService;
@@ -107,9 +111,17 @@ async fn main() -> anyhow::Result<()> {
};
tracing_subscriber::registry()
.with(fmt::layer().json().flatten_event(true))
.with(EnvFilter::from_default_env().add_directive("buzz_relay=info".parse()?))
.with(otel_layer)
.with(
fmt::layer()
.json()
.flatten_event(true)
.with_filter(log_env_filter(std::env::var("RUST_LOG").ok().as_deref())),
)
.with(otel_layer.map(|layer| {
layer.with_filter(telemetry::otel_env_filter(
std::env::var("BUZZ_OTEL_FILTER").ok().as_deref(),
))
}))
.init();
// Log any exporter-build failure now that the subscriber is installed.
@@ -146,6 +158,7 @@ async fn main() -> anyhow::Result<()> {
let db_config = DbConfig {
database_url: config.database_url.clone(),
read_database_url: config.read_database_url.clone(),
max_connections: config.db_pool_size,
..DbConfig::default()
};
let db = Db::new(&db_config).await.map_err(|e| {
@@ -1060,6 +1073,52 @@ async fn main() -> anyhow::Result<()> {
Ok(())
}
#[cfg(test)]
mod env_filter_tests {
use super::log_env_filter;
use buzz_relay::telemetry::otel_env_filter;
use tracing_subscriber::prelude::*;
#[test]
fn unset_enables_datastore_only_for_otel_filter() {
let logs = tracing_subscriber::registry().with(log_env_filter(None));
tracing::subscriber::with_default(logs, || {
assert!(!tracing::enabled!(target: "buzz_datastore", tracing::Level::INFO));
assert!(tracing::enabled!(target: "buzz_relay", tracing::Level::INFO));
});
let otel = tracing_subscriber::registry().with(otel_env_filter(None));
tracing::subscriber::with_default(otel, || {
assert!(tracing::enabled!(target: "buzz_datastore", tracing::Level::INFO));
});
}
#[test]
fn explicit_datastore_off_is_preserved_alone() {
assert_eq!(
otel_env_filter(Some("buzz_datastore=off")).to_string(),
"buzz_datastore=off"
);
}
#[test]
fn explicit_datastore_debug_is_preserved_alone() {
assert_eq!(
otel_env_filter(Some("buzz_datastore=debug")).to_string(),
"buzz_datastore=debug"
);
}
#[test]
fn log_and_otel_filters_are_configured_independently() {
assert_eq!(log_env_filter(Some("warn")).to_string(), "warn");
assert_eq!(
otel_env_filter(Some("buzz_relay=debug")).to_string(),
"buzz_relay=debug"
);
}
}
async fn run_community_revalidator(
state: Arc<AppState>,
period: std::time::Duration,
+76 -3
View File
@@ -4,8 +4,9 @@ use std::sync::atomic::Ordering;
use std::sync::Arc;
use axum::{
body::Body,
extract::{ConnectInfo, FromRequest, State, WebSocketUpgrade},
http::{HeaderMap, StatusCode},
http::{HeaderMap, Request, StatusCode},
middleware,
response::{IntoResponse, Json},
routing::{get, post, put},
@@ -16,7 +17,7 @@ use tower::ServiceExt;
use tower_http::cors::{AllowOrigin, CorsLayer};
use tower_http::limit::RequestBodyLimitLayer;
use tower_http::services::ServeDir;
use tower_http::trace::TraceLayer;
use tower_http::trace::{HttpMakeClassifier, TraceLayer};
use crate::api;
use crate::audio;
@@ -187,10 +188,23 @@ pub fn build_router(state: Arc<AppState>) -> Router {
merged
.layer(middleware::from_fn(track_metrics))
.layer(TraceLayer::new_for_http())
.layer(http_trace_layer())
.layer(build_cors_layer(&state.config.cors_origins))
}
fn http_trace_layer() -> TraceLayer<HttpMakeClassifier, fn(&Request<Body>) -> tracing::Span> {
TraceLayer::new_for_http().make_span_with(make_http_span as fn(&Request<Body>) -> tracing::Span)
}
fn make_http_span(request: &Request<Body>) -> tracing::Span {
tracing::info_span!(
target: "buzz_relay",
"http.request",
otel.kind = "server",
http.request.method = %request.method(),
)
}
fn is_admin_spa_path(path: &str) -> bool {
path == "/"
|| path == "/reports"
@@ -435,9 +449,14 @@ fn build_cors_layer(cors_origins: &[String]) -> CorsLayer {
mod tests {
use axum::{routing::get, Router};
use futures_util::SinkExt;
use opentelemetry::trace::TracerProvider as _;
use opentelemetry_sdk::trace::{InMemorySpanExporter, SdkTracerProvider};
use tokio::net::TcpListener;
use tokio::sync::mpsc;
use tokio_tungstenite::{connect_async, tungstenite::Message};
use tower::ServiceBuilder;
use tracing::Instrument as _;
use tracing_subscriber::prelude::*;
use super::*;
@@ -471,6 +490,60 @@ mod tests {
assert!(!should_serve_spa("/arbitrary", true));
}
#[tokio::test(flavor = "current_thread")]
async fn http_and_datastore_spans_are_exported_in_the_same_trace() {
let exporter = InMemorySpanExporter::default();
let provider = SdkTracerProvider::builder()
.with_simple_exporter(exporter.clone())
.build();
let subscriber = tracing_subscriber::registry().with(
tracing_opentelemetry::layer()
.with_tracer(provider.tracer("test"))
.with_filter(crate::telemetry::otel_env_filter(None)),
);
let _subscriber_guard = tracing::subscriber::set_default(subscriber);
let service = ServiceBuilder::new()
.layer(http_trace_layer())
.service(tower::service_fn(
|_: axum::http::Request<axum::body::Body>| async {
async {}
.instrument(tracing::info_span!(
target: "buzz_datastore",
"SELECT",
otel.kind = "client",
db.system.name = "postgresql",
))
.await;
Ok::<_, std::convert::Infallible>(axum::response::Response::new(
axum::body::Body::empty(),
))
},
));
service
.oneshot(
axum::http::Request::get("/")
.body(axum::body::Body::empty())
.unwrap(),
)
.await
.unwrap();
provider.force_flush().unwrap();
let spans = exporter.get_finished_spans().unwrap();
let http = spans
.iter()
.find(|span| span.name == "http.request")
.unwrap();
let datastore = spans.iter().find(|span| span.name == "SELECT").unwrap();
assert_eq!(
datastore.span_context.trace_id(),
http.span_context.trace_id()
);
assert_eq!(datastore.parent_span_id, http.span_context.span_id());
}
async fn handler_receives_message_with_limit(limit: usize, size: usize) -> bool {
let (received_tx, mut received_rx) = mpsc::unbounded_channel();
let app = Router::new().route(
+10
View File
@@ -25,6 +25,16 @@
use opentelemetry_otlp::ExporterBuildError;
use opentelemetry_sdk::{resource::EnvResourceDetector, trace::SdkTracerProvider, Resource};
use tracing_subscriber::EnvFilter;
/// Build the filter for spans exported through OpenTelemetry.
///
/// This is intentionally independent from `RUST_LOG`: changing stdout log
/// verbosity must not remove parent spans from exported traces. Set
/// `BUZZ_OTEL_FILTER` to override the default targets.
pub fn otel_env_filter(configured: Option<&str>) -> EnvFilter {
EnvFilter::new(configured.unwrap_or("buzz_relay=info,buzz_datastore=info"))
}
/// Build the OTEL [`Resource`] used by the trace provider.
///
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "buzz",
"private": true,
"version": "0.4.26",
"version": "0.5.0",
"type": "module",
"scripts": {
"dev": "vite",
+1 -1
View File
@@ -1010,7 +1010,7 @@ dependencies = [
[[package]]
name = "buzz-desktop"
version = "0.4.26"
version = "0.5.0"
dependencies = [
"anyhow",
"arboard",
+1 -1
View File
@@ -2,7 +2,7 @@
[package]
name = "buzz-desktop"
version = "0.4.26"
version = "0.5.0"
description = "Buzz desktop app"
authors = ["you"]
edition = "2021"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Buzz",
"version": "0.4.26",
"version": "0.5.0",
"identifier": "xyz.block.buzz.app",
"build": {
"beforeDevCommand": {