mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
## What
Implements a "bring your own harness" (BYOH) generic ACP mechanism —
replacing per-harness backend code with a data-driven 3-tier system:
- **Tier 1 (compiled-in builtins):** goose, claude, codex, buzz-agent —
unchanged behavior
- **Tier 2 (bundled presets):** cursor, omp, grok, opencode, kimi, amp,
hermes, openclaw, and any future additions — defined in
`PRESET_HARNESSES`, no code duplication, icons stay
TerminalSquare/bundled-asset-only
- **Tier 3 (user-defined custom):** JSON definitions saved to
`custom_harnesses/` under app data; managed via Settings → Agents UI
## Changes
### Core data model
- `HarnessDefinition` — id, label, command, args, env, install URL/hint
- `PRESET_HARNESSES` static table — single source of truth for all
presets; `preset_harness_ids()` derives reserved IDs (D-11: no
hand-maintained copy)
- `source: "builtin" | "preset" | "custom"` tagging on every catalog
entry
### Persistence (B-4, B-6)
- `save_custom_harness_to_dir(dir, definition, rename_old_id)` —
backup-swap atomic write (backs up target → .bak, commits temp → target,
restores .bak on failure, removes .bak on success); safe on Windows
where `fs::rename` over an existing file is "access denied"
- `save_and_warm` / `delete_and_warm` — hold `PERSIST_MUTEX` for the
write + registry-warm pair, eliminating the lost-update race (B-6) where
two concurrent saves could interleave their warm calls and leave a stale
registry snapshot
- Validate-before-mutate: both IDs and env validated before any
filesystem mutation
### Env validation boundary (B-3)
- `validate_harness_definition_pub` calls `validate_user_env_keys` on
definition env at save AND load
- Rejects malformed keys (BUZZ_AUTH_TAG=x forgery shape), reserved keys
(BUZZ_MANAGED_AGENT etc.), NUL bytes, oversized values
### TypeScript boundary (B-2 / Thufir CRITICAL)
- `RawAcpRuntimeCatalogEntry` now declares `definition_env?:
Record<string,string>` and `source: "builtin" | "preset" | "custom"`
- `fromRawAcpRuntimeCatalogEntry` maps `definition_env → definitionEnv`
(camelCase); absent field defaults to `{}`
- Edit form reads `entry.definitionEnv` — env no longer erased on
save-then-edit cycle
### Unified descriptor (Phase A / Thufir F4)
- `EffectiveHarnessDescriptor { command, args, env }` in `readiness.rs`
- `resolve_effective_harness_descriptor()` — single resolver used by
spawn, spawn_hash, summary, get_agent_models (both saved and unsaved),
and readiness
- No competing arg-resolution forms
### Other fixes
- B-5: stop freezing `runtime.defaultArgs` into `record.agent_args` on
normal create paths
- B-7: readiness exec-check — `MissingBinary` variant for custom
commands not found on PATH
- B-8: onboarding transition — `setTimeout(0)` removed, parent-owned
route intent via `navigateAfterComplete` prop
- C-9: collector-discriminating sweep tests with injectable filters
- C-10: `HarnessManagementCard` uses `harnessGalleryLogic` helpers
(killed duplicate filter/sort)
- D-11: `BUILTIN_IDS` derived from `PRESET_HARNESSES` (no
hand-maintained copy)
- D-12: `mobile/pubspec.lock` churn reverted
- D-13: false ownership fast-path comment fixed
- D-14: URL scheme validation for `installInstructionsUrl`
- D-15: OpenClaw Gateway env-locus README line
### Tests added
**B-4 persistence (6 tests):**
`save_to_dir_create_writes_file_and_loads_back`,
`save_to_dir_same_id_edit_replaces_content`,
`save_to_dir_backup_is_cleaned_up_after_same_id_edit`,
`save_to_dir_rename_removes_old_file_and_creates_new`,
`save_to_dir_rename_nonexistent_old_id_is_non_fatal`,
`save_to_dir_roundtrip_with_env_preserves_values`
**B-3 env validation (6 tests):**
`validate_rejects_malformed_key_with_equals_sign`,
`validate_rejects_reserved_key_buzz_managed_agent`,
`validate_rejects_reserved_key_case_insensitive`,
`validate_rejects_nul_byte_in_value`,
`validate_rejects_value_over_per_value_size_limit`,
`validate_accepts_well_formed_env`
**B-2 API boundary (4 TS tests in tauri.test.mjs):**
`fromRawAcpRuntimeCatalogEntry maps definition_env to definitionEnv`,
`defaults definitionEnv to {} when absent`, `preserves source preset`,
`env round-trips through edit payload shape`
## Preset catalog
| ID | Label | Command |
|----|-------|---------|
| `cursor` | Cursor | `cursor-agent acp` |
| `omp` | Oh My Pi | `omp acp` |
| `grok` | Grok Build | `grok agent --always-approve stdio` |
| `opencode` | OpenCode | `opencode acp` |
| `kimi` | Kimi Code | `kimi acp` |
| `amp` | Amp | `amp-acp` |
| `hermes` | Hermes Agent | `hermes-acp` |
| `openclaw` | OpenClaw | `openclaw acp` |
## Review-fix pass (2026-07-26, Eva)
Fixes from the three-way review (Wren / Dawn / Eva) in the
buzz-generic-acp-harnesses thread, pushed as new commits (no rewrite):
1. **installHint edit round-trip** — form seeding extracted to
`formValuesFromCatalogEntry` (single source of truth), input rendered,
full-definition lossless round-trip regression.
2. **Dangling-delete coherence** — delete allowed; confirm counts
referencing agents (direct pin + persona-inherited); summary rows render
`harness (deleted): <id>`; spawn errors become actionable sentences
(`user_facing_harness_error`); composed delete→summary→start test.
3. **Comma-in-args** — rejected at `validate_harness_definition` (shared
by save AND disk load), mirrored inline in the form.
4. **Registry publish race** — collision/dup filtering moved into
`load_custom_harnesses` (both loaders inherit shadowing rules);
discovery publishes by re-reading the dir under `persist_mutex` (lock
scoped to publish only); deterministic interleaving regressions for
save-during-discovery and delete-during-discovery.
5. **Mechanical** — discarded `belongs_to_us` sweep arg deleted,
`load_global_agent_config` hoisted out of the per-record summary loop,
duplicated doc paragraph + stray SAFETY comment removed.
6. **PGID test de-flaked** — leader kept alive through the assertion.
Known follow-up (filed in review, not blocking): file-size split-outs
queued in `check-file-sizes.mjs` entries.
## Gate table — head `bf53f1d60`
| Gate | Result |
|------|--------|
| `cargo test --lib` (desktop/src-tauri) | **1701 passed**, 0 failed, 14
ignored |
| desktop JS suite (`pnpm test`) | **3605 passed**, 0 failed |
| `tsc --noEmit` | clean |
| `biome check` + file-size/px/pubkey checks | clean |
| `cargo clippy --lib -- -D warnings` | clean |
| `cargo fmt --check` | clean |
PR head: `bf53f1d60e3cbd07392e1287b83bb37ba90d0d33` — includes merge of
origin/main (`c2a4ee711`, conflicts in agent_models composed with
#2890's live Databricks discovery)
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
137 lines
6.5 KiB
TOML
137 lines
6.5 KiB
TOML
[workspace]
|
|
|
|
[package]
|
|
name = "buzz-desktop"
|
|
version = "0.4.26"
|
|
description = "Buzz desktop app"
|
|
authors = ["you"]
|
|
edition = "2021"
|
|
|
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
|
|
[lib]
|
|
# The `_lib` suffix may seem redundant but it is necessary
|
|
# to make the lib name unique and wouldn't conflict with the bin name.
|
|
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
|
|
name = "buzz_lib"
|
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
|
|
|
[features]
|
|
default = ["system-keyring"]
|
|
mesh-llm = ["dep:iroh", "dep:mesh-llm-sdk", "dep:mesh-llm-host-runtime", "dep:mesh-llm-client", "dep:mesh-llm-node", "dep:mesh-llm-system", "dep:mesh-llm-events"]
|
|
# OS keyring backing for desktop secret storage (nsec private keys). When
|
|
# disabled, secrets fall back to 0o600 files. On by default for real builds.
|
|
system-keyring = ["dep:keyring"]
|
|
|
|
[build-dependencies]
|
|
base64 = "0.22"
|
|
serde = { version = "1", features = ["derive"] }
|
|
serde_json = "1"
|
|
tauri-build = { version = "2", features = [] }
|
|
|
|
[target.'cfg(unix)'.dependencies]
|
|
libc = "0.2"
|
|
ctrlc = { version = "3", features = ["termination"] }
|
|
|
|
[target.'cfg(target_os = "linux")'.dependencies]
|
|
keyring = { version = "3.6.3", default-features = false, features = ["sync-secret-service", "vendored"], optional = true }
|
|
# Used directly (alongside tauri-plugin-notification) so we can hold the posting
|
|
# D-Bus connection open. GNOME 46+ dismisses a notification the moment that
|
|
# connection is dropped, which the plugin does immediately. Default features
|
|
# keep the pure-Rust zbus backend, matching the plugin (no libdbus needed).
|
|
notify-rust = "4"
|
|
|
|
[target.'cfg(target_os = "macos")'.dependencies]
|
|
objc2-app-kit = { version = "0.3.2", default-features = false, features = ["NSHapticFeedback"] }
|
|
keyring = { version = "3.6.3", default-features = false, features = ["apple-native", "vendored"], optional = true }
|
|
security-framework = { version = "3.7.0", features = ["OSX_10_15"] }
|
|
window-vibrancy = "0.6"
|
|
user-idle = { version = "0.6", default-features = false }
|
|
plist = "1"
|
|
|
|
[target.'cfg(windows)'.dependencies]
|
|
windows-sys = { version = "0.61", features = ["Win32_Security", "Win32_Storage_FileSystem", "Win32_System_JobObjects", "Win32_System_Registry", "Win32_System_Threading", "Win32_Foundation"] }
|
|
keyring = { version = "3.6.3", default-features = false, features = ["windows-native", "vendored"], optional = true }
|
|
user-idle = { version = "0.6", default-features = false }
|
|
|
|
[dependencies]
|
|
atomic-write-file = "0.3"
|
|
anyhow = "1"
|
|
dirs = "6"
|
|
tauri = { version = "2", features = ["macos-private-api"] }
|
|
tauri-plugin-deep-link = "2"
|
|
tauri-plugin-opener = "2"
|
|
tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
|
|
tauri-plugin-window-state = "2"
|
|
tauri-plugin-dialog = "2"
|
|
tauri-plugin-updater = "2"
|
|
tauri-plugin-process = "2"
|
|
infer = "0.19"
|
|
hex = "0.4"
|
|
ed25519-dalek = "=3.0.0-rc.0"
|
|
tokio = { version = "1", features = ["fs", "sync", "rt", "macros", "time", "net", "io-util"] }
|
|
tokio-tungstenite = { version = "0.29", features = ["rustls-tls-webpki-roots"] }
|
|
tokio-util = { version = "0.7", features = ["rt"] }
|
|
bytes = "1"
|
|
futures-util = "0.3"
|
|
opus = "0.3"
|
|
neteq = { version = "0.8", default-features = false }
|
|
serde = { version = "1", features = ["derive"] }
|
|
serde_json = "1"
|
|
serde_yaml = "0.9"
|
|
toml = "0.8"
|
|
nostr = { version = "0.44", features = ["nip44"] }
|
|
zeroize = "1"
|
|
reqwest = { version = "0.13", features = ["json", "query", "stream", "blocking"] }
|
|
rustls = { version = "0.23", default-features = false, features = ["aws_lc_rs", "std"] }
|
|
url = "2"
|
|
buzz_core_pkg = { package = "buzz-core", path = "../../crates/buzz-core" }
|
|
buzz_persona_pkg = { package = "buzz-persona", path = "../../crates/buzz-persona" }
|
|
buzz_sdk_pkg = { package = "buzz-sdk", path = "../../crates/buzz-sdk" }
|
|
buzz_agent_pkg = { package = "buzz-agent", path = "../../crates/buzz-agent" }
|
|
iroh = { version = "1.0.2", optional = true }
|
|
mesh-llm-sdk = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "f455d493a2ae82baf2a326e2d0fda351433b4b30", package = "mesh-llm-sdk", default-features = false, features = ["client", "serving"], optional = true }
|
|
mesh-llm-host-runtime = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "f455d493a2ae82baf2a326e2d0fda351433b4b30", package = "mesh-llm-host-runtime", default-features = false, features = ["dynamic-native-runtime"], optional = true }
|
|
# Model catalog + hardware survey for the Share-compute model picker (same
|
|
# diagnose pattern as mesh-console). Lib name of mesh-llm-client is mesh_client.
|
|
mesh-llm-client = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "f455d493a2ae82baf2a326e2d0fda351433b4b30", package = "mesh-llm-client", optional = true }
|
|
mesh-llm-node = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "f455d493a2ae82baf2a326e2d0fda351433b4b30", package = "mesh-llm-node", optional = true }
|
|
mesh-llm-system = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "f455d493a2ae82baf2a326e2d0fda351433b4b30", package = "mesh-llm-system", optional = true }
|
|
mesh-llm-events = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "f455d493a2ae82baf2a326e2d0fda351433b4b30", package = "mesh-llm-events", optional = true }
|
|
base64 = "0.22"
|
|
sha2 = "0.11"
|
|
tar = "0.4"
|
|
bzip2 = "0.6"
|
|
chrono = { version = "0.4", features = ["serde"] }
|
|
tauri-plugin-global-shortcut = "2"
|
|
tauri-plugin-notification = "2.3.3"
|
|
uuid = { version = "1", features = ["v4", "v5"] }
|
|
png = "0.18"
|
|
# wayland-data-control: without it arboard is X11-only on Linux, so copies made
|
|
# in a Wayland session land in XWayland's clipboard where Wayland-native apps
|
|
# never see them (set_text still returns Ok). The backing wl-clipboard-rs dep is
|
|
# target-gated inside arboard; at runtime X11 sessions still fall back to X11.
|
|
arboard = { version = "3", features = ["wayland-data-control"] }
|
|
image = { version = "0.25", default-features = false, features = ["jpeg", "png", "webp", "gif"] }
|
|
zip = "8"
|
|
flate2 = "1"
|
|
sherpa-onnx = "1.12"
|
|
regex = "1"
|
|
rusqlite = { version = "0.37", features = ["bundled"] }
|
|
axum = "0.8"
|
|
rodio = "0.22"
|
|
earshot = "1.0"
|
|
rubato = "3.0"
|
|
audioadapter-buffers = "3.0"
|
|
tempfile = "3"
|
|
strip-ansi-escapes = "0.2"
|
|
tracing = "0.1"
|
|
|
|
[dev-dependencies]
|
|
# `test-util` enables tokio's paused-clock (`start_paused`) so the relay
|
|
# admission gate tests can assert exact wait durations without real sleeps.
|
|
tokio = { version = "1", features = ["test-util"] }
|
|
# The relay's media validation, so the snapshot-sharing tests can prove the
|
|
# full export → sanitize → relay-accept → import contract end to end.
|
|
buzz_media_pkg = { package = "buzz-media", path = "../../crates/buzz-media" }
|