mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
b450d469330a4ee2ffa069e2c2cee10caf54d786
708
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b450d46933 |
test(managed-agents): structural guard evidence for concurrency lock invariants
Replace timing-based concurrency tests with compile-enforced structural
guard ownership proofs per Thufir's binding shape consult.
Item 1 (generation lock): add SCOPE_GENERATION_TEST_LOCK to
test_fallback_relay_never_claims_during_identity_import and
test_scope_generation_guard_rejects_stale_scope_for_import; exhaustive
indirect-mutator sweep confirmed all remaining callers guarded.
Item 2 (writer test): widen on_after_restore to
FnOnce(&mut Vec<ManagedAgentRecord>, &MutexGuard<'_, ()>) — callback
borrows the actual store guard. Dropping or removing the guard before
the call is a compile error. Writer completes one full transaction
(lock → load → WRITER_EDIT → save → writer_committed) after records_loaded
releases it; on_after_restore saves COMP_SENTINEL under the live borrow.
Delete writer_at_store_lock pre-lock signal and all timing-based comments.
Item 3 (contender test): widen on_transition_acquired to
FnOnce(&MutexGuard<'_, ()>) in both start_pair_lazy_for_with_hook and
start_pair_for_with_hook — callback borrows the actual transition guard.
Drop or removal before the call is a compile error. Remove AtomicBool,
try_recv, not-fired-during assertion, and all ns-vs-ms timing rationale.
Proof by mutex exclusion: on_transition_acquired cannot execute during
compensation because both borrow the same mutex.
Production delegates unchanged: compensate_drain passes |_, _| {},
start_pair_lazy_for and start_pair_for pass |_| {} for on_transition_acquired.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
|
||
|
|
d71cd62c7f |
fix(desktop): resolve authorized-pass round-4 test-vs-claim residuals
Item 1 — contender test (production-called hook): Move start_pair_for_with_hook and start_pair_lazy_for_with_hook to runtime_commands_seams.rs (extracted to stay under the 1000-line gate; included via #[path]). Production start_pair_for delegates to start_pair_for_with_hook with no-op hooks; start_pair_lazy_for delegates through start_pair_lazy_for_with_hook. The test-file mirror start_pair_for_with_hook is deleted from runtime_commands_tests.rs. The contender test calls the production-callable seam: removing managed_agent_runtime_transition from start_pair_for_with_hook would also remove it from the production delegation chain, making the test a faithful proxy. Item 2 — writer test (on_after_restore callback): Remove the unconditional save_managed_agents_at from the production compensate_drain_with_hook body (step-7 was a no-op duplicate that also masked the real compensation result on error). Add on_after_restore hook invoked after compensate_drain_for returns, still under both guards. Production compensate_drain passes a no-op; test injects a mutation + save. The writer test: on_records_loaded signals writer and waits for writer_at_store_lock_rx before returning; on_after_restore mutates COMP_SENTINEL in-memory and saves (assert/unwrap). Both COMP_SENTINEL and WRITER_EDIT must appear on disk. If the store guard is dropped before on_after_restore, the writer interleaves, loads without COMP_SENTINEL, and the first assertion fails deterministically. Item 3 — E2E classification: thread-focus-mode.spec.ts:139 failed in run 30987602439 (branch head |
||
|
|
6739f157e2 |
test(desktop): resolve P4 pass-3 test-vs-claim defects in workspace-scoped agent store
Writer test: change writer to block directly on managed_agents_store_lock (not via start_pair_lazy_for_with_hook) so the test fails deterministically if the store guard is dropped before the step-7 save. The hook mutates records in-memory; the step-7 save writes the mutation to disk while the lock is still held; the writer loads after the lock is released. Contender test: add try_recv() check on a dedicated channel inside on_records_loaded alongside the existing atomic bool. on_transition_acquired sends to both channels. try_recv() is deterministic: without the transition lock, on_transition_acquired fires in nanoseconds; by the time on_records_loaded runs (after file I/O), the channel contains the message. Documents the time-differential argument explicitly in the test comment. Full-tail PID: capture child.id() before wrapping into ManagedAgentProcess; assert exact equality with receipt.pid (assert_eq! not assert! pid > 0). SCOPE_GENERATION_TEST_LOCK: add to all remaining tests that mutate or capture scope generation: app_state_scope_tests.rs (4 tests), global_agent_config_tests.rs (4 tests), runtime_commands_tests.rs (2 tests), scope.rs (5 generation tests). All tests that advance SCOPE_GENERATION now participate in the process-global serialization mutex. Trim runtime_commands.rs doc comments to stay at 1000 lines (gate limit). Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
27ea607243 |
fix(desktop): suppress await_holding_lock clippy lint on epoch tests
SCOPE_GENERATION_TEST_LOCK is a std::sync::Mutex held across await points in test_full_tail_stop_spawn_receipt_register_save and test_relay_mesh_preflight_precedes_stop to prevent concurrent tests from advancing the generation counter mid-test. The deadlock risk is acceptable in test-only code: the lock is never held across blocking operations, only across async coordination inside a single test. Suppress the lint explicitly with a comment explaining the rationale. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
7c455d9759 |
fix(desktop): serialize generation-sensitive epoch tests with SCOPE_GENERATION_TEST_LOCK
test_full_tail_stop_spawn_receipt_register_save and test_relay_mesh_preflight_precedes_stop both capture the scope generation via current_scope_generation() and call into restart_under_captured_epoch_for or restart_local_agent_on_config_change_for, which validate the generation under a lock. Without SCOPE_GENERATION_TEST_LOCK, a concurrent test calling next_scope_generation() can advance the counter between capture and validation, causing the epoch to return Skipped instead of the expected outcome. Both tests now hold SCOPE_GENERATION_TEST_LOCK for the duration of their execution, matching the serialization pattern used by the workspace-transition tests. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
45f199ba5d |
fix(desktop): resolve P4 pass-2 test-vs-claim defects in workspace-scoped agent store
Four IMPORTANT findings: each assertion now fails if the production guard or
save it claims to prove is removed.
Fix 1 — compensate_drain writer test (genuine store-lock contention):
Add compensate_drain_with_hook seam; on_store_acquired hook fires while BOTH
locks are held. Test writes COMP_SENTINEL under the lock, signals writer,
waits for writer to queue on the store lock, then releases. Final disk state
must contain BOTH COMP_SENTINEL and WRITER_EDIT — fails if the guard is
dropped before compensate_drain_for's save.
Fix 1b — start-contender test (production start-lock seam):
Add start_pair_lazy_for<R: tauri::Runtime> generic seam; production
start_managed_agent_runtime_pair_lazy and start_pair delegate to it.
Contender now calls start_pair_lazy_for (the seam the production adapter
calls) instead of a raw mutex lock — proves compensation blocks production
starts, not just an arbitrary lock acquisition.
Fix 2 — Record-Mesh TOCTOU (async production driver):
Drive restart_local_agent_on_config_change_for (full async driver). Injected
mesh_fn rewrites provider to relay-mesh after the driver has resolved
mesh_model_id=None; epoch re-resolves to Some("auto") != None → TOCTOU guard
fires → Skipped before stop. Removing the in-epoch re-resolve guard would
allow the epoch to proceed, calling stop_fn and failing the assertion.
Fix 3 — Helper-vs-helper serialization (pre-acquisition hook):
Add with_workspace_transition_preflight_with_hook and
install_client_under_workspace_transition_with_hook to mesh_llm_scope.rs.
Both serialization tests use a proper 3-step handshake: holder signals
lock_held BEFORE publishing state; contender fires pre-acquisition hook
signaling at_lock_boundary; only then does the holder publish and release.
Removing the shared lock would let the contender bypass the boundary,
inverting each test's expected outcome.
Fix 4 — Full-tail receipt and disk assertions:
write_receipt_fn captures all receipt fields: key.pubkey, key.relay_url,
pid, desktop_instance_id (asserted equal to app.config().identifier),
started_at. Final disk assertions verify last_started_at.is_some(),
last_stopped_at.is_none(), last_error.is_none() on the matching record.
Removing the post-spawn record save drops last_started_at and fails the
disk assertions.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
|
||
|
|
91aea2db96 |
fix(desktop): resolve P4 pass-1 review defects in workspace-scoped agent store
Fix all five IMPORTANT defects identified by Thufir's P4 pass-1 review: 1. Concurrency tests: both test_compensate_drain_writer_vs_compensation_deterministic and test_compensate_drain_concurrent_start_is_blocked now drive the production compensate_drain adapter (not compensate_drain_for directly). Transition guard passed by value; store lock and restore owned by the production symbol. Coordinator/contender ordering via channels/barriers exclusively — no thread::sleep. 2. Global-restart tests: cross-platform spawn_long_lived_child_for_test (sh loop / ping) and spawn_noop_child_for_test (sh exit 0 / cmd.exe exit 0) replace the UNIX-specific sleep 10000 and /usr/bin/true, fixing Windows Rust CI failures. Full-tail test asserts non-empty personas/teams/global in captured context. Context-load-failure test uses malformed JSON (not absent file) to exercise the genuine parse-error path. Record-mesh-change test seeds eligible runtime and constructs mismatched context.mesh_model_id to trigger in-epoch TOCTOU guard. 3. Active-scope identity import: import_identity routes through the production with_workspace_transition_preflight (mesh-llm) / direct workspace_transition (no mesh-llm) when has_active_scope, matching apply_workspace orchestration. Direction tests rewritten as helper-vs-helper: each side uses a production helper (with_workspace_transition_preflight or install_client_under_workspace_ transition); oneshot channels establish entry/release ordering with no direct lock acquisition and no sleep. 4. Team memory boundary: setup_team_import_app_with_scope adds a memory-bearing team member (member_with_memory); seam test asserts both the captured HTTP relay URL and the captured owner p-tag from the built engram event after after_store commits a distinct owner/scope. 5. CI portability: setup_import_app_with_scope and setup_team_import_app_with_scope use WorkspaceAgentScope::new with writable temp agent base so definitions_dir has the production <base>/scopes/<scope_id> shape, fixing the Linux /retention EPERM failure and the poisoned-lock cascade in seam tests. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
a1228b39a1 |
fix(desktop): replace map_or(false,...) with is_some_and per clippy
Resolve clippy::unnecessary-map-or in import_tests.rs from the P3 completion commit. No logic change. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
48015b838c |
fix(desktop): resolve P3 compliance gaps in Areas 3 and 4
Area 4 — capture scope BEFORE discovery in ensure_relay_mesh_for_record. Previously capture_active_scope was called after resolve_mesh_bootstrap_target; a workspace switch between discovery and capture would let the helper install a client against an endpoint discovered under the old scope while validating against the new scope. Move capture_active_scope above the discovery call so the captured scope and discovered endpoint are always from the same workspace. Area 4 — make with_workspace_transition_preflight production-callable. Change the transition body parameter from FnOnce() -> Result<T> (sync) to FnOnce() -> BoxFuture<'static, Result<T>> (async) so production callers that dispatch spawn_blocking and await the result keep the workspace_transition guard alive across the full async body. Extract apply_workspace_body as a standalone async fn; apply_workspace (mesh-llm feature) routes through with_workspace_transition_preflight so the helper is no longer test-only dead code. The cfg_attr(not(test), allow(dead_code)) annotation is removed. Update the two test call sites to pass BoxFuture-returning closures. Area 3 — add memory-bearing fixture and owner-coordinate assertion. Add minimal_agent_snapshot_json_with_memory which carries one core memory entry (MemoryLevel::Core). Rewrite test_agent_switch_between_store_and_profile_finishes_captured_outbound to use this fixture so submit_memory actually fires in Phase 4. The injected MemoryPublish adapter now asserts both that relay_url contains the captured relay URL and that the built engram event's p tag (owner counterpart/coordinate) equals the captured owner's pubkey hex, not the post-switch owner committed in after_store. Add extract_p_tag_from_event_json helper. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
c29cd651db |
fix(desktop): extract mesh readiness helpers to satisfy file-size ratchet
mesh_llm.rs was 1016 lines (split-count 1017, gate limit 1000). Extract MeshReadinessFailure, classify_mesh_readiness_failure, mesh_readiness_failure_message, and wait_for_mesh_inference into a new mesh_llm_readiness.rs submodule (140 lines). mesh_llm.rs is now 887 lines (split-count 888). All other files remain under the 1000-line limit. Tests that used the readiness items via use super::* add explicit use super::readiness::* imports since the items moved out of the parent namespace. No behavior change. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
8b046bc62b |
fix(desktop): wire production callers through shared mesh preflight helpers
apply_workspace and import_identity now call run_mesh_transition_preflight (extracted from fail_if_client_mesh_active path) rather than inlining the check. ensure_relay_mesh_for_record captures scope before discovery and routes the install call through install_client_under_workspace_transition, which validates full scope identity (scope_id, relay, owner, generation) under the workspace_transition guard before invoking the closure. with_workspace_transition_preflight gains run_mesh_transition_preflight as a companion: tests continue to call the callback helper directly; production callers that need spawn_blocking dispatch use run_mesh_transition_preflight after acquiring workspace_transition themselves. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
50f615ecbd |
Merge remote-tracking branch 'origin/main' into duncan/workspace-scoped-agent-store
* origin/main: chore(release): release Buzz Desktop version 0.5.5 (#4809) feat: paste composer text without formatting (#4801) Revert "chore(release): release Buzz Desktop version 0.5.5" (#4808) Signed-off-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> |
||
|
|
09207bcf29 |
fix(desktop): skip OS keychain in test builds to prevent headless hangs
hydrate_keys and persist_agent_keys both call the OS keychain through the SecretStore global singleton. In headless test environments (CI, locked keychain) these calls block on the macOS Security daemon IPC (SecKeychainFindGenericPassword / SecKeychainItemModifyAttributesAndData) and cause tests to hang indefinitely. Three tests in the Phase-3 seam suite were hanging: - test_record_mesh_change_after_preflight_aborts_before_stop - test_full_tail_stop_spawn_receipt_register_save - test_relay_mesh_preflight_precedes_stop All three write agent records with non-empty pubkeys to disk then call load_managed_agents_at or save_managed_agents_at, triggering keychain IPC. Fix: gate hydrate_keys and persist_agent_keys with #[cfg(test)] early returns. Test builds exercise the keychain-generic testable cores (hydrate_keys_with / persist_agent_keys_with) via mock KeyStore impls directly; the production wrappers are not exercised in unit tests and must never be. Also add SecretStore::warm_cache_for_test for use by any future test that needs to pre-seed the in-process cache without touching the OS keychain. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
8342dfcc58 |
chore(release): release Buzz Desktop version 0.5.5 (#4809)
## Buzz Desktop release v0.5.5 - **Frozen main:** `25a9cf1be6d245fbd7373cb1160dbc790baf5bd5` - **Reviewed candidate:** `8380c1f8ead8816bcf1f4ea9f66aa08e2441b15a` - **Previous desktop release:** `desktop-v0.5.4` - **Proposed immutable tag:** `desktop-v0.5.5` This PR must be **squash merged** only after the Desktop Release Candidate check passes. The branch must remain based directly on current `main`; stale base, payload drift, incomplete notes, or an unauthorized merge produce no tag. The checked-in changelog accounts for every non-merge commit in the release range. Publication remains bound to the immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com> |
||
|
|
25a9cf1be6 |
feat: paste composer text without formatting (#4801)
## Summary - handle Cmd+Shift+V on macOS and Ctrl+Shift+V on Windows/Linux in the message composer - read plain text through the native Tauri/arboard clipboard path in packaged builds, with a browser-only Clipboard API fallback - re-enter ProseMirror's paste pipeline with populated `text/plain` clipboard data so selection, undo, multiline behavior, and paste observers remain intact - cover both platform mappings with rendered composer E2E tests that assert the native command path ## Testing - `pnpm test` — 4,286 passed - `pnpm check` - `pnpm typecheck` - `pnpm exec playwright test composer-selection-formatting.spec.ts --project=smoke` — 26 passed - `cargo check --manifest-path desktop/src-tauri/Cargo.toml --workspace --all-targets --target aarch64-apple-darwin` - `just desktop-tauri-test` — 2,206 core tests plus integration and doc-test groups passed - full pre-push hooks passed ## Manual verification Physical packaged-app clipboard verification remains recommended on macOS, Windows, and Linux. The automated E2E uses mocked Tauri IPC but asserts the native `read_clipboard_text` command is invoked. Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> |
||
|
|
4634fe1b39 |
chore(desktop): merge origin/main into workspace-scoped-agent-store
Adopt spawn_hash → spawn_snapshot rename from main; migrate spawn_config_hash: 0 test fixtures to prospective_spawn_config_snapshot() calls in global_agent_config tests, epoch tests, and runtime_commands tests. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
79c52166cf |
Revert "chore(release): release Buzz Desktop version 0.5.5" (#4808)
Reverts block/buzz#4800 |
||
|
|
a0ed13de14 |
chore(release): release Buzz Desktop version 0.5.5 (#4800)
## Buzz Desktop release v0.5.5 - **Frozen main:** `4a2305170eef565bf1836e2859247e67c030f8af` - **Reviewed candidate:** `2d03d37b05b68186b2caad9da79080032be3ac72` - **Previous desktop release:** `desktop-v0.5.4` - **Proposed immutable tag:** `desktop-v0.5.5` This PR must be **squash merged** only after the Desktop Release Candidate check passes. The branch must remain based directly on current `main`; stale base, payload drift, incomplete notes, or an unauthorized merge produce no tag. The checked-in changelog accounts for every non-merge commit in the release range. Publication remains bound to the immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com> |
||
|
|
d0658609ed |
fix(desktop): split Phase 3 test files to satisfy 1000-line size ratchet
Four test files exceeded the 1000-line gate introduced by Phase 3 seam work. Split each by extracting the largest block into a sibling file included via #[path], keeping every file at or below the limit: - global_agent_config.rs (1987 → 946): inline test block moved to global_agent_config_tests.rs (637) + global_agent_config_epoch_tests.rs (420) - mesh_llm_tests.rs (1052 → 776): Area-4 serialization tests extracted to mesh_llm_transition_tests.rs (284) - team_snapshot/tests.rs (1087 → 896): Area-3 phase-boundary seam tests extracted to team_snapshot/seam_tests.rs (200) - runtime_commands_tests.rs (1056 → 777): concurrency tests extracted to runtime_commands_concurrency_tests.rs (289) All nine files are now under 1000 lines. No test logic changed; all 2230 lib tests pass. just desktop-check passes at this commit. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
1a1571b165 |
fix(desktop): resolve clippy warnings introduced in Phase 3 seam commits
- items_after_test_module: move SCOPE_GENERATION_TEST_LOCK before mod tests in scope.rs - await_holding_lock: add #[allow] + SAFETY comments on all tests holding std::sync::Mutex across .await (single-threaded tokio runtime, no deadlock risk) - needless_borrow: drop redundant & on &handle calls to async import cores - too_many_arguments: suppress on spawn_agent_child_at (8-arg function required by approved Area-5 signature; bounded to two call sites) - redundant_closure: replace wrapper closures with function-item references for stop_managed_agent_process and write_agent_runtime_receipt - unused_imports: remove unused load_managed_agents_at from two test imports - cloned_ref_to_slice_refs: replace &[x.clone()] with std::slice::from_ref(&x) in three test save_managed_agents_at calls - doc_lazy_continuation: add two extra spaces to continuation lines in the team_snapshot.rs docstring list item 3 - dead_code: suppress on make_captured_scope helper prepared for future tests Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
4a2305170e |
fix: reauthenticate databricks model discovery (#4008)
## Summary - preserve Databricks catalog 401 responses as authentication failures and retry discovery exactly once after silently refreshing the rejected bearer - preserve runtime OAuth recovery: when discovery has no usable OAuth credential, `session/new` succeeds with only the trimmed configured model so the first `session/prompt` can run the existing browser PKCE flow - reject a rejected configured `DATABRICKS_TOKEN` with actionable, non-interactive guidance; static credentials cannot recover through PKCE - use the configured-model fallback for non-auth discovery failures without caching failed or fallback catalogs, so later sessions retry discovery - keep known Databricks v2 models only for authenticated empty-catalog responses and mark their provenance - resolve discovery before MCP spawn or session registration, preventing failed discovery from leaking resources or consuming session capacity - permit serialized interactive PKCE only from the explicit saved-agent model picker; passive draft discovery never opens a browser ## Runtime flow 1. OAuth discovery attempts cached credentials and silent refresh without opening a browser. 2. If no usable OAuth bearer exists, `session/new` advertises only the configured model and succeeds. 3. The first `session/prompt` uses `TokenSource::bearer()`, which may launch browser PKCE. 4. A later session retries discovery and caches only the authenticated catalog. ## Regression coverage - rejected-but-locally-fresh OAuth bearer performs one refresh and one catalog retry - OAuth mode with no cached token allows `session/new` and returns exactly the trimmed configured model - the OAuth fallback is not cached; a later authenticated session retries discovery and caches the returned catalog - rejected static tokens still reject `session/new` - failed discovery does not consume the sole session slot or spawn the supplied MCP process - Desktop interactive/passive auth intent, static-token redaction, and authenticated empty-catalog provenance ## Verification - `cargo test -p buzz-agent` - `cargo test --manifest-path desktop/src-tauri/Cargo.toml --lib commands::agent_models` - `cargo clippy --manifest-path desktop/src-tauri/Cargo.toml --all-targets -- -D warnings` - `cargo fmt --all -- --check` - `git diff --check` - full pre-push hooks ## Review Adversarial review found and drove fixes for session/MCP resource leakage, duplicate concurrent PKCE flows, sensitive error propagation, incorrect 403 reauthentication, missing discovery-level coverage, passive browser launch, and the Desktop file-size ratchet. The final follow-up preserves the existing prompt-time OAuth flow while retaining static-token rejection and pre-allocation discovery ordering. --------- Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> |
||
|
|
8faf09f9ae |
Revert "chore(release): release Buzz Desktop version 0.5.5" (#4797)
Reverts block/buzz#4788 |
||
|
|
fcfd41dbff |
test(desktop): area-4 serialization direction tests for workspace transition helpers
Adds three tests to commands/mesh_llm_tests.rs exercising the lock-serialization contract between with_workspace_transition_preflight and install_client_under_workspace_transition: - test_active_client_stop_then_transition_preflight_succeeds: installs a mock client, calls production mesh_stop_client (clears the runtime slot), then calls with_workspace_transition_preflight; asserts fail_if_client_mesh_active sees an absent runtime and the transition body executes. - test_transition_held_queued_install_detects_stale_scope: holds workspace_transition directly, advances the generation counter and commits a distinct scope; spawns an install task that queues on the lock; after the guard drops, install_client_under_ workspace_transition acquires, detects the stale captured scope (generation + full identity mismatch), and returns Err without invoking the injected install closure. - test_install_held_transition_preflight_observes_client: holds workspace_transition directly and places a mock client runtime in AppState; spawns a transition task that queues on the lock; after the guard drops, with_workspace_transition_preflight acquires, runs fail_if_client_mesh_active, observes the installed client, and returns Err. All three tests call the production helpers directly. No port (127.0.0.1:9337) is touched. Generation-sensitive tests acquire SCOPE_GENERATION_TEST_LOCK to avoid cross-test interference. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
1dc537dad5 |
fix(desktop): area-3+4 snapshot import seams, transition helpers
Area 3 — snapshot import phase seams: - Extract confirm_agent_snapshot_import_core and confirm_team_snapshot_import_core, generic over tauri::Runtime. - Add before_store (post-entry-capture, pre-Phase-3a-lock) and after_store (post-Phase-3a-lock, pre-Phase-3b) hooks; no-ops in production. - ProfilePublish<'a>/MemoryPublish<'a> borrowed arg structs — no secret-key cloning across closures. - Thin Tauri commands call cores with no-op hooks and real relay adapters; app.state::<AppState>() inside async move blocks avoids non-'static borrows. - Delete duplicate submit_engram_event from team_snapshot.rs; reuse import.rs version (pub(crate)) for both agent and team engram boundaries. - Add retain_team_pending_in_scope(scope, team) sibling in teams.rs; team snapshot Phase 3 calls it instead of live retain_team_pending to avoid re-resolving active scope after a possible switch. - Move egress-guard + avatar tests from import.rs into import_tests.rs (included via #[path]); update egress inventory and allowlists. - Add SCOPE_GENERATION_TEST_LOCK in scope.rs for cross-module serialization of generation-sensitive tests; agent + team seam tests share this lock. - 4 named seam tests all pass concurrently (verified with cargo test --lib). Area 4 — workspace transition helpers: - Extract with_workspace_transition_preflight: acquires workspace_transition, runs fail_if_client_mesh_active, invokes transition_body under guard. - Extract install_client_under_workspace_transition: acquires workspace_transition, validates full captured scope identity (scope_id, relay, owner_pubkey, generation) under guard, calls install. - Both helpers live in mesh_llm_scope.rs alongside fail_if_client_mesh_active. - Area 4 tests (3 named) implemented in mesh_llm_tests.rs (separate commit). Also: remove unused AppHandle import from nest.rs (zero warnings). Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
4c665aeac3 |
fix(desktop): serialize tray channel actions for frontend (#4762)
## Summary - serialize native `openChannel` tray actions with the camelCase field names consumed by the TypeScript frontend - prevent a valid tray channel ID from becoming `/channels/undefined` - add a Rust serialization contract test covering the complete frontend payload shape ### Root cause `TrayAction` renamed the enum variant to `openChannel`, but its struct fields still serialized as `channel_id` and `community_generation`. The frontend reads `action.channelId`, so tray navigation called `goChannel(undefined)`. ### Testing - manually verified the corrected runtime payload and tray navigation before removing temporary logging - `just desktop-ci` - pre-push hooks (desktop checks/tests, Tauri checks, and Rust tests) --------- Signed-off-by: Kalvin Chau <kalvin@block.xyz> Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Co-authored-by: npub122y0pqkertljmedu303rl0aqrj3w8pvu43t6jxm6875lzg6f2pwqegc3xc <5288f082d91aff2de5bc8be23fbfa01ca2e3859cac57a91b7a3fa9f12349505c@buzz.block.builderlab.xyz> Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> |
||
|
|
b948c54792 |
chore(release): release Buzz Desktop version 0.5.5 (#4788)
## Buzz Desktop release v0.5.5 - **Frozen main:** `383d9e1eafd569b44b9c835200dba69ef7cec9dc` - **Reviewed candidate:** `ac589061ef1009f55384536e483cfe9b1260697b` - **Previous desktop release:** `desktop-v0.5.4` - **Proposed immutable tag:** `desktop-v0.5.5` This PR must be **squash merged** only after the Desktop Release Candidate check passes. The branch must remain based directly on current `main`; stale base, payload drift, incomplete notes, or an unauthorized merge produce no tag. The checked-in changelog accounts for every non-merge commit in the release range. Publication remains bound to the immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com> |
||
|
|
e30db7028f |
feat(projects): support multiple repositories (#4671)
## Summary - adopt the finalized NIP-MP project model so one project can enumerate and switch between multiple NIP-34 repositories - add project and repository navigation, activity summaries, existing-repository attachment, and repository access-channel management - preserve privacy-safe activation provenance for agent-authored patches, pull requests, issues, and associated commits ## Test plan - [x] Run desktop typecheck and unit tests - [x] Run focused NIP-MP, repository access, and provenance tests - [x] Run Rust formatting and desktop lint checks - [x] Run the complete pre-push suite after merging current `main` - [ ] Manually verify project creation, repository attachment, switching, and access repair on staging - [ ] Manually verify public-channel and private-agent origin labels on newly created Git activity Related: [#4695](https://github.com/block/buzz/pull/4695) --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
1ec287f935 |
fix(desktop): area-2+5 captured restart context, epoch_for, teams param
CapturedRestartContext struct prepared fallibly before any stop: loads personas, teams, and global config from captured definitions_dir with ?, verifies owner keys against captured_scope.owner_pubkey and derives owner_hex from keys, resolves effective Mesh model ID for pre-stop preflight. Failure in any pre-stop step returns Skipped without stopping the running agent. restart_under_captured_epoch_for: injected stop_fn/spawn_fn/write_receipt_fn (all FnMut for multi-relay support); core owns key construction, receipt construction, runtimes.insert with context.scope.scope_id, captured-dir saves; in-epoch re-resolve of Mesh model detects non-workspace TOCTOU edits (Skipped before stop); stop failure classified as Skipped/ stop-failed-before-irreversible, not FailedAfterStop. spawn_agent_child_at: teams: &[TeamRecord] parameter added; live wrapper loads live teams; captured epoch passes context.teams loaded fallibly from definitions_dir; internal live load_teams call removed. Area-1 completion: adds two missing concurrency tests test_compensate_drain_writer_vs_compensation_deterministic (channel- established ordering, BOTH effects on disk) and test_compensate_drain_concurrent_start_is_blocked (channel/barrier, contender blocked until transition guard released). Area-2 tests: all six Thufir-named tests implemented and passing (context_load_failure, mesh_preflight_failure, workspace_switch, record_mesh_change, full_tail_stop_spawn_receipt_register_save, relay_mesh_preflight_precedes_stop). Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
ce3cf3cd25 |
Polish Huddle voice controls (#4694)
## Summary - add a visible Stop control for interrupting agent speech - make push-to-talk available by default while preserving manual mute controls - refine agent management, muted audio states, drawer layering, and return navigation - suppress duplicate notification sounds for Huddle messages ## Why Huddles could trap users behind long agent speech, hide useful agent controls, and leave temporary Huddle state visible after the call. The drawer also regressed when the terminal substrate began painting behind the rounded app surface. ## Validation - `just desktop-ci` - focused Huddle Playwright coverage for the drawer, speech interruption, agent picker, and leave navigation --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
5179726737 |
fix(local-archive): default both archive settings to enabled (#4750)
## Overview
Both local archive settings — "Archive my agents' observer frames" (kind
24200) and "Archive my agents' turn metrics" (kind 44200) — previously
defaulted to OFF in OSS builds, controlled by build-time env vars. This
had an irreversible cost: observer frames are ephemeral (not stored by
the relay), so any missed events are permanently unrecoverable. This PR
makes both settings default to enabled for all builds and removes the
build-time flag machinery entirely.
## What changed
### Rust
- `observer_archive_default_enabled()` — returns `true` unconditionally;
removed `option_env!("BUZZ_DESKTOP_BUILD_OBSERVER_ARCHIVE_DEFAULT")`
check and `nest_is_dev()` runtime fallback.
- `agent_metric_archive_default_enabled()` — returns `true`
unconditionally; removed
`option_env!("BUZZ_DESKTOP_BUILD_AGENT_METRIC_ARCHIVE_DEFAULT")` check
and its OSS-build test.
- `build.rs` — removed both `rerun-if-env-changed` declarations
(`BUZZ_BUILD_OBSERVER_ARCHIVE_DEFAULT`,
`BUZZ_BUILD_AGENT_METRIC_ARCHIVE_DEFAULT`) and the two baked-env
emitting blocks.
### Build / CI
- `Justfile` — removed `desktop-tauri-test-compiled-flags` recipe (the
dual-compile test machinery).
- `.github/workflows/ci.yml` — removed the "Desktop Tauri compiled-flag
verification" CI step.
### TypeScript
- `useObserverArchiveSeed.ts` — removed `observerArchiveDefaultEnabled`
dep from `ObserverArchiveSeedDeps` and the `policyOn` gate in
`reconcileObserverArchive`; the function now unconditionally calls
`mergeSaveSubscriptionKinds`.
- `useAgentMetricArchiveSeed.ts` — removed
`agentMetricArchiveDefaultEnabled` dep from `AgentMetricArchiveSeedDeps`
and the `defaultOn` flag-check path in `maybeSeed`; the
`hasExplicitChoice` guard is preserved as the sole gate against
re-seeding.
- `LocalArchiveSettingsCard.tsx` — removed `policy` prop,
`observerPolicy` state, and `observerArchiveDefaultEnabled` fetch from
`ObserverArchiveSection`; toggle is now always enabled (just `toggling`
disables it); removed the stale "Always on for internal builds" copy
branch; removed the `observerPolicy !== false` guard from
`handleObserverToggle`.
- `tauriArchive.ts` — updated JSDoc on both default-enabled functions to
reflect always-true.
- `e2eBridge.ts` — changed both mock defaults from `?? false` to `??
true` so E2E tests without an explicit mock override exercise the real
default behavior.
### Tests
- `useObserverArchiveSeed.test.mjs` — replaced `policyOn` dep with
direct merge dep; updated `test_oss_policy_off_no_merge` →
`test_reconcile_always_seeds_24200`; all cancellation, identity-switch,
and ordering tests adapted.
- `useAgentMetricArchiveSeed.test.mjs` — removed `defaultOn` dep and
`test_oss_build_does_not_seed`; updated
`test_internal_build_unset_seeds_*` → `test_default_enabled_*`;
`hasExplicitChoice` guard tests unchanged.
## Preservation of explicit opt-outs
Users who have previously toggled the setting off are unaffected:
- `useAgentMetricArchiveSeed` skips seeding when
`hasExplicitChoice(pubkey)` returns true (localStorage-persisted per
identity).
- Observer archive reconciliation now unconditionally calls
`mergeSaveSubscriptionKinds`, but a user who already deleted the
subscription can turn it off via the Settings toggle, which calls
`removeSaveSubscriptionKind` — this is the existing explicit opt-out
path, and the toggle is now always enabled (not locked by a policy
flag).
## Result
- No `BUZZ_BUILD_*_ARCHIVE_DEFAULT` /
`BUZZ_DESKTOP_BUILD_*_ARCHIVE_DEFAULT` references remain outside
CHANGELOG/history.
- Desktop node tests: 4168 pass, 0 fail.
- `just desktop-tauri-check`: clean.
- `just desktop-tauri-test`: all pass.
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
|
||
|
|
3957d738a2 |
fix(desktop): area-1 compensation lock order — adapter acquires store lock before delegating to lock-free core
Move managed_agents_store_lock acquisition, scope-generation validation, and load_managed_agents_at into the compensate_drain adapter; compensate_drain_for becomes a lock-free testable core that accepts records: &mut [ManagedAgentRecord] and start_fn: FnMut(&DrainJournalEntry, &mut [ManagedAgentRecord]). Store guard is held continuously through validate→load→restore→save so any store-lock-only writer is serialized on the store lock (not the transition guard). The transition guard is still received by value from the caller to ensure it stays alive through the entire compensation. Test coverage: - test_compensate_for_restarts_stopped_entries_in_order - test_compensate_for_reports_partial_restart_failure - test_compensate_for_start_fn_receives_records_slice - test_compensate_drain_empty_stopped_returns_none_with_real_app - test_compensate_drain_stale_scope_skips_all_with_real_app - test_compensate_drain_attempts_restart_and_reports_degradation_with_real_app Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
0c33a8a55f |
fix(agents): canonicalize stale persona harness pins (#4631)
Replace the stale `agent_command_override` drop logic in `apply_persona_snapshot` with a three-tier canonical command resolver. ## What this fixes The old code dropped a create-time harness pin when the persona switched to a different runtime, but it had two failure modes: 1. **Preset harnesses invisible.** `known_acp_runtime_exact()` only searches `KNOWN_ACP_RUNTIMES` (builtins). Preset harnesses such as OpenClaw live in `PRESET_HARNESSES`, so the destination lookup returned `None` and the outer `if let` branch never executed — a Goose→OpenClaw persona switch left the stale Goose override in place, keeping the agent running Goose instead of OpenClaw. 2. **Pin-side canonical resolution incomplete.** The pin was resolved by `known_acp_runtime()`, which searches by id/command/alias and returns a `&KnownAcpRuntime` entry correctly. However, if the *pin* named an alias (e.g. `claude-code-acp`) and the *destination* was a preset harness absent from builtins, the outer guard still failed for the same reason as (1). The alias regression test pins the requirement that the canonical resolver must handle both sides: alias pins must be recognised and drops must fire when the destination is a known preset. ## How it works now `canonical_harness_command(input)` accepts any form a stored override can take — bare command, alias, path prefix, or runtime id — and resolves it to the harness primary command through three tiers: 1. **Builtins** — `KNOWN_ACP_RUNTIMES`, matched by id/command/alias. 2. **Static presets** — `PRESET_HARNESSES`, matched by id or normalised command. 3. **Loaded registry** — custom/preset definitions loaded at runtime. `command_for_runtime_id` (id-only input, same three tiers) replaces the two-step `known_acp_runtime_exact`/`lookup_loaded_harness_by_id` pattern in `record_agent_command`, `effective_agent_command`, and `try_record_agent_command`, adding the static preset tier so preset harnesses resolve correctly even without a warm registry. ## Changed files - `discovery/presets.rs` — `preset_command_for_id`, `command_for_runtime_id`, `canonical_harness_command` - `discovery.rs` — re-export new functions; make `normalize_command_identity` `pub(crate)`; refactor three command-resolution functions to use `command_for_runtime_id` - `custom_harnesses.rs` — `loaded_harness_registry` visibility `fn` → `pub(super)` (needed by `canonical_harness_command`) - `persona_events.rs` — replace two-step `known_acp_runtime_exact`/`known_acp_runtime` + pointer comparison with canonical-command comparison - `persona_events/stale_pin_tests.rs` (new) — four regression tests: Goose→OpenClaw drop, OpenClaw→Goose drop, claude-code-acp alias→OpenClaw drop, same-harness path keep - `persona_events/tests.rs` — `sample_record`/`sample_persona` exposed as `pub(super)` for the new test module Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1g8493u0xfsjrvflg4n08ezd7vec99mnwzlv0qgwpr9d7gvjwhuzqx59rhw <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz> |
||
|
|
f86dfc5883 |
feat(desktop): surface config diff in restart-required badge (#3637)
The "Restart required" badge reports that an agent's running config has
drifted from its spawn-time config, but never says what changed. This
ships the full feature: a typed Rust diff engine and a TS/UI layer that
renders it at every badge site.
## Rust core (spawn-snapshot diff engine)
Replaces the lossy `u64` `spawn_config_hash` with a typed
`SpawnConfigSnapshot`. The snapshot is stamped from the already-resolved
command/env/config values immediately before `spawn()`, closing the race
window where a mid-spawn config edit would suppress the badge.
`SpawnConfigSnapshot::canonical()` is the single JSON projection shared
by the badge and the diff. Drift is `to_value(stamped) !=
to_value(current)`; the diff is a generic leaf walk over those same two
values, so badge-on and diff-non-empty are structurally guaranteed.
Adding a snapshot field reaches the UI with no code change to the diff
engine — `mutation_table_covers_every_serialized_field` fails CI if a
new field arrives without a mutation row.
`eligible_restart_diff(persona_orphaned, Option<TrackedSpawnState>)`
returns the final vector — snapshot walk entries plus a synthetic
`adapter_availability` entry. It returns empty for an orphaned instance
(spawning one would fail) and for agents with no tracked spawn state
(never stamped, can never have drifted). `needs_restart =
!restart_diff.is_empty()` derives from that vector and nothing else.
Redaction policy (`policy_for(path)`) is shared by the wire diff and the
snapshot's manual `Debug` via `is_safe_to_reveal()` from
`managed_agents::env_vars` as the single authority for env-key masking:
| Policy | Paths | Rendering |
|---|---|---|
| `Text` | `system_prompt`, `team_instructions` | character counts only
|
| `MaskedBare` | `args`, `relay_url` | `••••`, no suffix |
| `MaskedSuffix` | non-allowlisted `env.*` | `••••` + last 4 chars when
longer than 8 |
| `Plain` | allowlisted `env.*` (`BUZZ_AGENT_THINKING_EFFORT`,
`BUZZ_AGENT_PROVIDER`, `BUZZ_AGENT_MODEL`, `DATABRICKS_HOST/MODEL`) and
everything else | verbatim |
Default-deny: every env key not in the explicit allowlist stays masked.
`is_safe_to_reveal()` is the single allowlist authority for both the
baked-env display and the diff.
`restart_diff` is omitted from the wire when empty
(`skip_serializing_if`).
## TypeScript / UI layer
New `restartDiff.ts` module defines `RestartDiffEntry`, `RestartChange`,
`JsonValue`; `tauri.ts` and `types.ts` re-export and add `restart_diff`
/ `restartDiff` fields (Rust omission → `restartDiff: []`).
**`RestartDiffBadge`** — hover tooltip capped at 6 entries + "and N
more", `asChild` span trigger (never inside a `<button>`), auto-restart
blurb below the diff list (on/off variant from `autoRestartEnabled`
prop; same `AUTO_RESTART_ON_BLURB` / `AUTO_RESTART_OFF_BLURB` constants
shared with the Runtime-tab banner). **`RestartDiffList`** renders the
full uncapped list for the Runtime-tab banner with `tooltip`/`inline`
presentation variants for correct foreground in both surfaces.
**`ManagedAgentRow` B4 fix** — badge moved to a sibling `div` of the row
expansion button; tooltip trigger has no `button` ancestor.
**`UnifiedAgentsSection`** — both badge sites render
`<RestartDiffBadge>` instead of a raw `<Badge>`, with
`autoRestartEnabled` threaded from `agent.autoRestartOnConfigChange`.
**Side-panel fix** — `RestartDiffBadge` rendered tab-independently in
the `ProfileSummaryView` hero area (was Runtime-tab only — root cause of
the ~50% inconsistency Will reported). Hero badge is `self-center` in
the flex column. `ProfileRuntimeTabContent` early-return checks
`needsRestart` so the banner is never dropped when all other content is
empty. Auto-restart blurb in the Runtime-tab banner uses the shared
constants.
## Wire shape
```jsonc
"restart_diff": [
{ "field": "model", "change": { "kind": "value", "before": "gpt-5", "after": "claude-4" } },
{ "field": "system_prompt", "change": { "kind": "text", "before_chars": 1234, "after_chars": 1410 } },
{ "field": "env.OPENAI_API_KEY", "change": { "kind": "masked", "before": "••••bc12", "after": "••••xyz9" } },
{ "field": "env.BUZZ_AGENT_THINKING_EFFORT", "change": { "kind": "value", "before": "medium", "after": "high" } }
]
```
`added`/`removed` occur only for dynamic-map keys; nullable struct
fields always serialize as `null`; arrays are atomic leaves (`args`,
never `args.0`).
## Tests
**Rust** — 1902 passing: snapshot mutation coverage, diff entry
serialization, allowlist-aware env masking
(`allowlisted_env_key_shows_plain_value`,
`allowlisted_env_key_is_case_insensitive`,
`non_allowlisted_env_key_stays_masked`),
`unstamped_agent_yields_no_badge_and_no_entries` (both orphan values),
`summary_without_drift_omits_restart_diff_from_the_wire`,
`unstamped_availability_is_not_drift`. Clippy clean, fmt clean.
**TypeScript** — `needs-restart-screenshots.spec.ts`: 11 E2E cases
registered in the smoke project — all three badge sites, tooltip +
keyboard focus, DOM no-button-ancestor assertion, 6+1 truncation,
uncapped Runtime list, unknown field humanisation, side-panel badge on
default Info tab, inactive/friendly-error Runtime opening path.
Consolidates [#3652](https://github.com/block/buzz/pull/3652)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
|
||
|
|
f18a9cb106 |
Defer desktop media uploads until send (#4522)
## Summary - send desktop messages immediately while media uploads continue in background state across channel navigation - show immediate progress above the composer and keep Jump to latest above it - report the real media stages as Preparing, Processing, Converting, Uploading, and Finishing - use Buzz's shared spinner during local media work, then switch to the real percentage when byte transfer begins - animate phase-label and status-suffix changes without overlap or layout jumps - keep cancel, progress fill, message publication, and community-reset behavior coordinated with the background task - use raw Tauri IPC for large browser files so renderer-side byte serialization does not block initial feedback ## Why Desktop previously blocked sending while attachments uploaded in the composer. Large videos could also pause the renderer before progress appeared, and the progress pill said Uploading while native media processing was still underway. This makes the initial response immediate and describes the work actually happening. ## Validation - `cd desktop && pnpm check` - `cd desktop && pnpm typecheck` - `cd desktop && pnpm test` (3,931 passed) - `cd desktop && pnpm exec vite build --mode e2e` - `cd desktop && pnpm exec playwright test tests/e2e/file-attachment.spec.ts --project=smoke` (11 passed) - focused native media tests (80 passed) - native Clippy with all targets and features - pre-push native suite (2,107 passed, 14 ignored; 3 diagnostics passed) Updated phase snapshots are included in the PR comments. Split from #4512 so the desktop and mobile changes can be reviewed independently. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
e73458efef |
fix(desktop): resolve clippy empty_line_after_doc_comments in file-size extractions
Two extraction sites had doc comment blocks adjacent with blank lines, triggering clippy::empty_line_after_doc_comments: - team_snapshot.rs: moved mod/use declarations before the doc block of confirm_team_snapshot_import (no longer adjacent to the doc comment). - runtime_commands.rs: inserted a non-doc // separator line directly between the compensate_drain and compensate_drain_for doc blocks (no blank line between // and /// so clippy treats them as connected). Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
ff16a80b5b |
fix(desktop): pass-2 review corrections (round 3) — captured respawn, behavioral tests, residue
Global-config respawn: replace live-scope wrappers with one transition epoch.
restart_under_captured_epoch acquires managed_agent_runtime_transition +
managed_agents_store_lock, validates captured generation, stops, builds a
captured spawn context (records/personas/global config/owner hex/scope_id
from captured_scope), spawns/registers, saves — no live wrapper past the stop.
persist_last_error takes captured_scope, validates generation under its own
acquired store lock, never called while that lock is held, fails closed on
poison. Two tauri::test::mock_app() tests: fresh-scope no-runtime → Skipped
(proves generation guard passes + path proceeds), stale-scope → Skipped at
generation step (switch-between-stop-and-spawn test, calls production fn).
Behavioral tests on production paths: tauri = { version = "2", features = ["test"] }
added to dev-dependencies. compensate_drain_for extracted with injectable
start_fn; tests call it directly (compensate_drain_for is the production core).
capture_agent_snapshot_import_entry and capture_team_snapshot_import_entry
extracted as testable entry guards; tests call the real production functions
(no-scope reject, owner-mismatch reject, matching-owner passes + relay verified,
stale-generation rejects validate_scope_generation). registerNestNotifications
extracted from useNestNotifications; test imports and calls the real function,
asserts all three event registrations, workspace-degraded toast payload,
unlisten cleanup per event. fail_if_client_mesh_active and mesh_stop_client
tested via tauri::test::mock_builder() with real AppHandle (no-runtime,
client-runtime, no-runtime stop paths). Generalized fail_if_client_mesh_active
and mesh_stop_client to tauri::Runtime to allow mock_app usage.
Residue: _compensation_gate_removed placeholder deleted from AppState.
identity.rs:346-348 comment corrected (guard passed by value, not dropped).
mesh_llm_scope.rs:59-64 stale re-arm comment replaced with accurate Option A note.
Duplicate generation bump at identity.rs:544 removed (clear_active_scope
calls next_scope_generation internally; no second bump needed).
File-size gate: AgentSnapshotImportEntry + capture_agent_snapshot_import_entry
extracted to import_entry.rs (import.rs: 995 lines); TeamSnapshotImportEntry +
capture_team_snapshot_import_entry extracted to team_snapshot_entry.rs
(team_snapshot.rs: 997 lines). Both within the 1000-line ratchet.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
|
||
|
|
6dc4e29155 |
Merge origin/main into duncan/workspace-scoped-agent-store
Resolves two conflicts against main:
AppShell.tsx: HEAD had const { activeCommunity, reinitKey } = communitiesHook
for the composite workspace key; origin/main added useHuddlePresentation()
destructuring from the Huddle redesign (#4281). Resolution keeps both: the
composite key is required for useManagedAgentRuntimeReconciliation, and the
Huddle hooks are needed for the new Huddle UI.
MeshComputeSettingsCard.tsx: HEAD had the Stop using shared compute affordance
plus the legacy inline model section; origin/main (#3735) replaced the inline
model section with the MeshModelPicker component. Resolution keeps the Stop
button block and adopts the MeshModelPicker layout, discarding the replaced
inline model controls.
Also corrects the false comment at runtime_commands_tests.rs:342-345 that
claimed compensate_drain is covered by the desktop integration test suite.
The compensation round-trip requires an AppHandle; the codebase has no
tauri::test harness and no AppHandle mock. The honest coverage statement is:
drain-prefix contract proven by unit test, restart path integration-only.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
|
||
|
|
40607d4a88 |
fix(desktop): pass-1 review corrections (round 2) — compensation, Mesh stop, scope, CI
Item 1 — Compensation primitive: replace AtomicBool gate with lock-owning
compensate_drain that takes the caller's already-held rt_transition guard
by value, re-acquires only the store lock, validates captured scope generation,
then restores journal entries via start_pair_under_held_locks. Split
start_pair_under_held_locks out of start_pair so both the normal and
compensation paths share the spawn-and-register body. execute_drain_journal
refactored to accept an injectable stop_fn via drain_journal_with_stop;
execute_drain_journal_with_stop_fn exposed for test injection. Tests: live
SIGKILL drain, structural lock-release proof, deterministic partial-failure
test with injected stop error covering stopped prefix and remaining tail.
Item 2 — Client stop + start serialization: mesh_stop_client Tauri command
in mesh_llm_scope.rs stops only a client-mode runtime; serve/absent are
no-ops. Client start (ensure_relay_mesh_for_record) acquires
workspace_transition through runtime installation to serialize against
apply_workspace, which holds workspace_transition from before the Option A
preflight through commit. fail_if_client_mesh_active preflight runs under
workspace_transition so no new client can start in the check→commit gap.
UI: 'Stop using shared compute' button in MeshComputeSettingsCard shown when
isConsuming; calls new meshStopClient() in tauriMesh.ts. e2eBridge mock for
mesh_stop_client added.
Item 3 — Fallible migrations + atomic marker: rename_provider_to_runtime_in_personas
propagates Result; migrate_agent_keys_to_dev_service_at returns Result and
propagates from copy_agent_keys_between_stores. run_scoped_migrations uses ?
on persona-provider step. _ready marker written via temp+rename (atomic).
dev-key migration skipped in unit-test builds (#[cfg(not(test))]) to avoid
macOS Keychain dialogs. Tests: old-marker upgrade (no scope deletion), partial
migration failure withholds v1 until repair succeeds.
Item 4 — Global-config captured respawn: Phase 2 restart validates captured
scope generation under store lock before stop, and again before respawn via
start_local_agent_pairs_with_preflight_at (new captured variant using
definitions_dir). persist_last_error validates generation under store lock.
Item 5 — Snapshot imports captured operation context: both confirm_agent_snapshot_import
and confirm_team_snapshot_import capture owner keys at entry, verify against
captured_scope.owner_pubkey immediately, thread captured keys through all
mint/retention/engram phases. Re-verify owner key under store lock before
Phase 3a write. Outbound profile/memory phases use captured_scope.relay_url.
Item 6 — CI red: rustfmt applied (agents_scoped.rs, import.rs); clippy
needless_borrow at team_snapshot.rs:793 fixed; e2eBridge apply_workspace mock
returns { applied: true, degraded: [] } in both immediate and delayed branches;
mesh_stop_client mock case added. Stale 3-line doc fragment removed from
mesh_llm.rs; visibility of re-exported agents_scoped fns bumped to pub(crate).
Item 7 — Listener behavioral test: useNestNotifications.test.mjs exercises
workspace-degraded toast payload, unlisten cleanup, and boundary payloads
without requiring a real Tauri runtime. Doc comment updated: event-sync dispatch
failure does not emit workspace-degraded (shutdown-time, no toast surface).
Item 8 — Dead code (minor): backfill_persona_snapshots AppHandle shim removed;
stale scope_init.rs:388-393 comment corrected; make_base_dir test helper removed.
File-size gate: mesh_llm.rs (999), import.rs (999), team_snapshot.rs (999).
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
|
||
|
|
631b05c883 |
feat: ship Buzz Term (#4347)
## Summary - ship **Buzz Term** end to end: the terminal engine/runtime, mounted desktop substrate, and user-visible naming - add Quinn's tape-deck-inspired banner: a beveled chassis filled by the `buzz term` wordmark, surrounded by a complete-hex field - derive the wordmark's three-stop sweep from each theme's terminal palette so primary, secondary, and accent roles remain visibly distinct across all 62 shipped themes, including light themes - paint the banner once on its own pointer-transparent canvas; PTY rendering beneath it remains unchanged ## Banner behavior - uses the renderer's shared `8.4 × 17` cell metrics and production aspect ratio `2.0238` - regenerates only for viewport/theme changes; palette switches repaint correctly while the banner is visible - dismisses on non-empty output from the active terminal session; empty output and inactive sessions do not dismiss it - fails closed below **70 columns** rather than squeezing or clipping the wordmark - adds **8 lines** to `terminalRenderer.ts` for shared cell metrics and **zero lines inside `paint()`** ## Screenshots | Buzz (light) | Buzz Dark | |---|---| |  |  | | Kanagawa Lotus (light) | Red | |---|---| |  |  | Additional production-aspect finals: [Vesper](https://buzz.block.builderlab.xyz/media/9ca6514b63f8cfb2107a85ca46f16a940c0883848e6fbc718e411af94aa13100.png), [Min Dark](https://buzz.block.builderlab.xyz/media/f67bd2970e5d64ffb07b1ae78ab58c847e6ebc23e7e7a48e067eb024dba64ec8.png), and [Dark Plus](https://buzz.block.builderlab.xyz/media/290fee08924f37d064abc687ecf3e9526ab05b87e8e56d610f23048949793dbe.png). The screenshot harness was checked against the shipped painter at this exact head: all **2,541 draw calls** matched on color, glyph, x, and y; four deliberate divergence controls fired. ## Verification at `98ebc8f9048bd5f0ceb7e843b67874d642f0b7fd` - desktop tests: **3,946 / 3,946** - TypeScript: clean - checks: pass (two pre-existing informational `useTemplate` notices only) - integration/e2e: PASS (independent exact-SHA lane; artifacts recorded in the originating Buzz thread) - artifact/dead-path sweep: clean - redteam G1–G7: PASS - all six named banner emitter-deletion mutants die - independent handwritten five-row full-wordmark fixture kills Quinn's seven-mutant battery, including a one-pixel glyph change - real `112 × 46` canvas-rect dismissal tests separately cover active non-empty, active empty, and inactive non-empty output - layer-drop and zero-draw painter mutants die; z-order and pointer-events verified - CI's `tsc && vite build` includes all three banner modules - performance at DPR 2 (worst-case measured envelope): - one-time content paint: **~0.7–0.8 ms**, paid only when the banner is built or its palette changes - busy compositor, CSS `1277 × 697`, backing `2554 × 1394`: **470–497 µs/frame** for the full banner (**2.82–2.98%** of a 60 Hz frame) - busy compositor, CSS `1920 × 1080`, backing `3840 × 2160`: **1,139–1,212 µs/frame** (**6.83–7.27%**) - empty, one-glyph, and full-banner controls converge: compositor cost follows backing-layer area and DPR rather than painted-cell count - in the actual idle welcome state, cost is below both vsync-clamped rigs' resolution; it is not claimed as zero - **Pane cross-rig spread: resolved at matched loop rate.** Two independent rigs initially differed 2.3× (58–68 vs 136 µs/Mpx of backing store; pane, CSS 1277×697 / backing 2554×1394, DPR 2). The cause of *that* spread is rAF loop rate: the higher figure came from a free-running loop at ~1600fps. Throttled to ~200–236fps, both rigs read 58–68 µs/Mpx (1.25–1.44% of a 60Hz frame). The busy-composite figures quoted above remain the **unthrottled worst case** and are conservative by ~2.3× at the pane. Not established: the mechanism and sign of free-running distortion (one rig under-charges ~15%, the other over-charges 2.3×), and the 1080p figure has not been re-measured throttled. - the layer paints only on generation/theme/resize and dismisses on first non-empty active-session output, so the measurable busy cost is a short-lived worst case rather than a persistent PTY paint-path tax ## Follow-ups in this PR These are intentionally subsequent commits after the certified static-banner head, not claims about `98ebc8f90`: 1. close the compositor metrology: remeasure the 1080p point throttled and characterize the opposite-sign free-running rAF distortion, with each measurement regime stated 2. add Tyler's animated honeycomb color waves, gated by `prefers-reduced-motion`, a full 62-theme phase-sweep contrast check, and DPR-2 per-tick performance certification 3. land the already-proven mounted theme-switch regression probe from `RESEARCH/BUZZ_TERM_G3A_PROBE/` 4. bound the slow/hang-shaped G1-c mutant `waitFor` 5. optionally trim the generator to its ink bounding box, reducing the minimum viewport from 70 to 62 columns --------- Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com> Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz> Signed-off-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz> Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz> Co-authored-by: npub1cc3ha7z055mu0rwwu7806t2wt8mj3pvu0uv5mfp2c50dahaqhczshdalg6 <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz> Co-authored-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz> Co-authored-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz> Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> |
||
|
|
b29c8cdaa4 |
feat(desktop): redesign the Huddle experience (#4281)
## Summary - open Huddles in a focused companion window with a clean handoff back to the in-app drawer and backing channel - redesign the participant film strip, sidebar control, transcript surface, and themed shell treatment - preserve microphone and device control across windows, start agent voice on the first reply, and show agent speaking activity in the film strip - give each agent a distinct session voice, beginning with the configured default, plus compact per-agent text-to-speech and voice controls - enroll only agents explicitly mentioned or deliberately added through an agent panel into the live Huddle roster - keep temporary Huddle channels out of the sidebar unless the user explicitly brings one into the main app - remove Huddle-only avatar policy badges and filter short silence or noise segments before speech-to-text posts ## Why The previous flow exposed the temporary channel as product UI, obscured who was present or speaking, and split transcript and audio state between the main and companion windows. This keeps backing channels as implementation details unless a user explicitly brings a Huddle into the app, while sharing the live conversation and audio lifecycle across both surfaces. Agent participants now join only after an explicit invitation, distinct voices make multi-agent Huddles easier to follow, and short microphone noise no longer becomes stray transcript messages. ## Validation - `pnpm check` - `pnpm build:e2e` - `pnpm exec playwright test tests/e2e/huddle-transcription.spec.ts --project=smoke` (13 passed) - Huddle sidebar visibility unit coverage (4 passed) - focused managed-agent and persona-mention E2E coverage (2 passed) - `pnpm test` (3,910 passed) - `cargo clippy --manifest-path desktop/src-tauri/Cargo.toml --all-targets -- -D warnings` - `cargo test --manifest-path desktop/src-tauri/Cargo.toml` (2,093 passed, 14 ignored; 3 diagnostics passed) --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
985cdcc6ea |
feat(agents): model-tuning parity in global Agent Defaults editor (#4578)
## Overview
The global Agent Defaults surface (Settings card, defaults modal,
onboarding) exposed structured controls for Effort but left Max Output
Tokens, Context Limit, and Max Rounds as raw env vars. Per-agent dialogs
had structured numeric fields but only for `isBuzzAgentRuntime` —
incorrectly excluding Goose. This PR unifies numeric-tuning capability
across all surfaces, fixes a pre-existing dual-editor defect, and adds
full test coverage.
## What changed
### Phase 1 — Catalog projection
- Add `max_rounds_env_var` to `KnownAcpRuntime` in `runtime_metadata.rs`
(`Some("BUZZ_AGENT_MAX_ROUNDS")` for buzz-agent, `None` elsewhere).
- Project all three numeric env-var fields (`max_tokens_env_var`,
`context_limit_env_var`, `max_rounds_env_var`) end-to-end:
`AcpRuntimeCatalogEntry` Rust struct, TS `types.ts`,
`RawAcpRuntimeCatalogEntry` + `fromRawAcpRuntimeCatalogEntry` in
`tauri.ts`, and the e2e mock bridge (`withMockRuntimeConfigMetadata`).
### Phase 2 — Field model
- `deriveAgentConfigFieldModel` now derives `maxOutputTokens` /
`contextLimit` / `maxRounds` descriptors from catalog-projected fields.
- `structuredEnvKeys(descriptors)` — exported helper that takes the
**rendered** descriptor set (not the whole model). Hidden keys follow
what is actually rendered per surface: global hides effort + all three
numeric keys for buzz-agent / two for Goose; per-agent buzz-agent hides
effort + three numeric keys; per-agent Goose hides only its two numeric
keys. `BUZZ_AGENT_THINKING_EFFORT` stays a visible generic env row
per-agent because no effort control renders there.
### Phase 3 — UI
- Extract `NumericTuningFields` from `buzzAgentModelTuningFields.tsx` as
a shared descriptor-driven component (`descriptors`, `envVars`,
`inheritedEnvVars`, `onEnvVarChange`). Kind-specific minima:
`NUMERIC_KIND_MIN` map (`maxOutputTokens`/`contextLimit`: 1,
`maxRounds`: 0) applied to `<input min>`.
- **Global surface** (`AgentConfigFields.tsx`): deduplicate the
previously duplicated Advanced env-editor block; render
`NumericTuningFields` below the env editor when descriptors exist;
`hiddenKeys` and `bakedGenericRows` exclusions use `structuredEnvKeys`
so structured keys are never double-rendered. Under 1000 lines.
- **Per-agent surfaces** (`EditAgentAdvancedFields`,
`PersonaAdvancedFields`): replace `isBuzzAgentRuntime` as the
numeric-field gate with `deriveNumericDescriptors(selectedRuntime)` from
`agentConfigCore`; hidden keys come from
`structuredEnvKeys(numericDescriptors)` — the same rendered descriptor
set, no local rebuilding (fixes pre-existing dual-editor defect).
Catalog status carried as `RuntimeCatalogStatus` (`loading | ready |
error`); both error and loading withhold structured controls and leave
saved values visible as generic rows, making error distinguishable from
"runtime not capable" (`ready` + no runtime).
- **Dialogs** (`AgentDefinitionDialog`, `AgentInstanceEditDialog`,
callers): `AgentDefinitionDialog` accepts `runtimeCatalogStatus?:
"loading" | "ready" | "error"` (replaces separate
`runtimesLoading`/`runtimesError` booleans); all call sites —
`AgentManagementDialogs`, `AgentsView`, `RequestedAgentCreateDialogs`,
`UserProfilePersonaDialogs` — compute and pass the status.
### Phase 4 — Tests
- `buildRecord` exported from `EnvVarsEditor.tsx` as a pure `(nextRows,
value, requiredKeys, hiddenKeys) => Record<string, string>` helper for
isolation testing.
- **17 new node tests** in `agentConfigCore.test.mjs`:
`deriveNumericDescriptors` (all three fields, partial, undefined
runtime, matches field-model subset); `structuredEnvKeys` per surface
including discriminating Goose per-agent effort-key invariant;
`NUMERIC_KIND_MIN` values.
- **4 new node tests** in `EnvVarsEditor.test.mjs`: hidden tuning key
preserved through generic row edits; runtime-switch then generic edit
(derives both descriptor sets, asserts new-runtime hidden key survives
`buildRecord` via `hiddenKeys` and old-runtime key survives via generic
rows); baked numeric key excluded via `filterBakedGenericRows` with
`numericTuningPlaceholder` assertion; clearing a structured override —
`numericTuningPlaceholder` verifies placeholder text.
- **5 new Playwright tests** in `agent-numeric-tuning.spec.ts` (added to
smoke project `testMatch`): global numeric fields visible for
buzz-agent; global: non-capable runtime hides numeric controls; Goose
per-agent shows `Inherit (16384)` after saving global value through the
UI; delayed catalog: saved values visible as generic rows while loading
then structured controls appear after settle; failed catalog: saved
values remain visible as generic rows (never the "unsupported" empty
state).
## Result
- buzz-agent global defaults: Max output tokens, Context limit, Max
rounds as structured inputs with `Inherit (N)` placeholders from baked
env.
- Goose global defaults: Max output tokens, Context limit as structured
inputs.
- A Goose global value surfaces as `Inherit (<value>)` in the per-agent
Goose edit dialog.
- No structured key is editable in two places on any surface; no
persisted key has zero editors.
- No `runtime.id === "buzz-agent"` comparison decides numeric-field
visibility anywhere — capability flows catalog →
`AcpRuntimeCatalogEntry` → field model → UI.
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
|
||
|
|
d4a4570b97 |
fix(desktop): clarify inherited agent parallelism (#4010)
## Summary - show an unambiguous `App default (10)` inherited state for parallelism in create and edit forms - explain that blank inherits the app default and suppress create-form number steppers that could silently set `1` - align the E2E mint fallback with production while preserving explicit input → definition → app-default precedence ## Why The forms displayed `1` even though an untouched field is omitted and desktop minting materializes `10`. The create-form spinner could also turn blank/inherited into an explicit `1` with one click while leaving the field looking nearly unchanged. ## Testing - `pnpm test` (desktop: 3,886 passed) - `pnpm typecheck` (desktop) - `pnpm check` (desktop) - pre-push `desktop-check` and `desktop-test` --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
f1fb83c45b |
fix(desktop): resolve file-size gate violations (import.rs, scope_init.rs)
Move scope_init tests to scope_init_tests.rs via #[path] include to bring scope_init.rs under the 1000-line ratchet (603 lines after extraction). Move test_outbound_relay_uses_captured_scope_not_live_state from import_avatar_tests (in import.rs) to the adjacent tests.rs to bring import.rs under the 1000-line limit (999 lines after move). Both files previously crossed the limit after the resume-pass corrections added the versioned-ready test block and the captured-relay test. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
ad3230b480 |
fix(desktop): resume-pass corrections — Option A Mesh, versioned ready, captured scope, lock-aware compensation
Eight corrections from the resumed loop (fresh 3-pass budget, Option A ruling):
1. Option A Mesh: delete pre-prepare drain_mesh_client_if_stale and rollback
restore_mesh_sharing (compensated a drain that no longer happens). Replace
with fail_if_client_mesh_active preflight in both apply_workspace and
identity import. Journaled Mesh recipe deferred as tracked follow-up.
2. Versioned _ready: scope_is_ready now reads marker content and compares
against READY_MARKER_VERSION ("v1"); old unversioned markers return false
and force re-run through run_pre_ready_family. Delete log-only post-ready
best-effort guards (backfill + retention migration) from apply_workspace.
Add test: old marker -> pipeline re-runs -> version advances.
3. Snapshot outbound phases use captured scope relay: both
confirm_agent_snapshot_import (Phase 3b profile) and
confirm_team_snapshot_import (Phases 4/5 profile + memory) now use
captured_scope.relay_url instead of relay_ws_url_with_override.
Add test proving outbound relay is captured-scope, not live-state.
4. Generation checks atomic with writes: global_agent_config Phase 1 validates
scope generation inside the store lock before writing config; Phase 2
(restart_local_agent_on_config_change) validates under lock before stop.
collect_restart_candidates renamed to collect_restart_candidates_at with
definitions_dir parameter. Mesh recovery helpers (persist_mesh_last_error_at,
clear_mesh_last_error_if_set_at) take captured_scope and validate generation
inside the store lock.
5. Lock-aware compensation gate: AtomicBool
managed_agent_drain_compensation_in_progress added to AppState.
compensate_drain sets it true (Release) before restarting entries, false
after. start_pair loads it (Acquire) before taking the transition lock and
returns Err if set. Closes the drop-then-compensate interleave window
without recursive locking. Add deterministic partial-drain test.
6. Pre-scope migrations deleted: migrate_agent_keys_to_dev_service (AppHandle
variant) removed from storage.rs. Pre-scope calls removed from
run_boot_migrations_inner. Scoped variants in run_pre_ready_family are
authoritative.
7. Degradation wired to UI: workspace-degraded Tauri event listener added to
useNestNotifications.ts (toast.error with payload as description). False
comment about emit_workspace_degradation removed from event_sync.rs.
backfill_persona_snapshots_at (dead lock-taking wrapper) deleted.
8. e2e test docs: test_two_workspace_relay_partition comment corrected --
Direction 3 asserts len==1 (B's event), not zero. Explicit note added that
this test does not cover desktop workspaces or substitute for the live probe.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
|
||
|
|
651f637275 |
chore(release): release Buzz Desktop version 0.5.4 (#4562)
## Buzz Desktop release v0.5.4 - **Frozen main:** `6de85fe31d781122756aecf954bae7d357a56b9a` - **Reviewed candidate:** `5836cb8f0af478ed3ee3bc6464a20fa4cc91303f` - **Previous desktop release:** `desktop-v0.5.3` - **Proposed immutable tag:** `desktop-v0.5.4` This PR must be **squash merged** only after the Desktop Release Candidate check passes. The branch must remain based directly on current `main`; stale base, payload drift, incomplete notes, or an unauthorized merge produce no tag. The checked-in changelog accounts for every non-merge commit in the release range. Publication remains bound to the immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com> |
||
|
|
80315ac1a6 |
fix(desktop): harden Windows installs against Defender block and orphaned Node (#4382)
This PR fixes two Windows-specific install failures: Windows Defender blocking the bare `irm|iex` PowerShell install command, and managed Node shims pointing at a version-bumped (now-absent) Node directory. The Defender block (Trojan:Win32/Commando.A!ml) fires before PowerShell runs and is not clearable via Allow. The Node orphaning means shims in the managed npm prefix resolve but fail at runtime with 'node not recognized' because they reference the deleted old Node path. - Replace all three Windows CLI install commands (Goose, Claude, Codex) with a two-step shape — `Invoke-RestMethod` to a named temp file, then execute — to eliminate the dropper signature; a new `windows_install_command!` macro in `discovery/windows_install.rs` generates all three strings at compile time so the shape cannot drift between runtimes - `$ErrorActionPreference='Stop'` aborts on download failure instead of falling through to a missing-file exit-0; `exit $LASTEXITCODE` propagates the vendor script's own exit code - Add `probe_node(executable, expected_version, timeout)` as a bounded seam: stdout goes to a temp file (not a pipe) so no exit path can block on an inherited handle; the child runs in its own process group on Unix so an unconditional group SIGKILL on every exit path terminates all descendants; on Windows `taskkill /T /F` provides the same tree-wide cleanup; `managed_node_runtime_ready()` is a thin wrapper that resolves the managed Node path and calls the seam - Add `resolve_adapter_path()` in `managed_node.rs`: resolves the candidate first, then calls `should_invalidate_adapter()` — a pure predicate that returns `true` only when the resolved path is under `buzz_managed_npm_bin_dir()` AND the managed Node runtime is orphaned; external adapters outside the managed prefix are always preserved Note: CI cannot reproduce the Defender block (no live Defender ML classifier). Proof of fix is structural — the command shape no longer matches the dropper signature. Canary validation on a real Windows machine with Defender enabled is the definitive check. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> |
||
|
|
01c80aa9b3 |
fix(desktop): save key backups to authorized path (#4022)
**Category:** fix **User Impact:** Users can save password-protected identity backups directly to protected macOS folders such as Downloads. **Problem:** Signed macOS builds could not save a portable `.ncryptsec` backup to Downloads because the atomic writer created an unauthorized sibling temporary file. This surfaced as an “Operation not permitted” error after the user completed backup creation. **Solution:** Portable exports now write only to the exact path authorized by the native Save panel, sync and verify the saved bytes, and refuse to truncate an existing backup. Buzz’s app-managed backup retains its atomic writer and durability guarantees. <details> <summary>File changes</summary> **desktop/src-tauri/src/commands/export_util.rs** Clarifies that secret exports use a dedicated writer compatible with native Save-panel authorization. **desktop/src-tauri/src/commands/identity.rs** Routes portable NIP-49 exports through the Save-panel-compatible writer while preserving canonical app state. **desktop/src-tauri/src/key_backup.rs** Adds an exclusive-create portable writer with owner-only permissions, disk sync, byte verification, and cleanup on failure. Keeps the existing atomic writer for app-managed backups. **desktop/src-tauri/src/key_backup_tests.rs** Covers portable export permissions, absence of sibling files, and preservation of existing backups. </details> ## Reproduction steps 1. Install a signed macOS build containing this change. 2. Open **Settings → Profile → Private key → Create backup** and complete backup creation. 3. Save a fresh `identity.ncryptsec` file into `~/Downloads` and confirm Buzz reports success. 4. Open and verify the saved backup with its password. 5. Repeat the save using an existing filename and confirm Buzz preserves the existing file and asks for a new filename. ## Verification - Full desktop Tauri suite: 2,049 passed, 14 ignored - Diagnostic suite: 3 passed - Focused backup coverage: 30 passed - Tauri clippy (`--all-targets -D warnings`), Rust formatting, and `git diff --check`: passed - Push hooks: org safety, branch skew, and desktop Tauri checks passed Signed-production Downloads smoke remains required after merge because the signing workflow is restricted to `main`. Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz> |
||
|
|
f810a2f49e |
fix(desktop): make OpenAI key re-enterable after first save in card mint dialog (#4140)
Fixes a write-once dead-end in the card mint dialog where a user with an
expired OpenAI key had no way to replace it.
**Source-aware key status (Rust + TypeScript).** `card_mint_key_status`
returns a layer discriminant (`"none" | "global" | "persona" | "agent" |
"process"`) instead of a boolean. A pure `resolve_key_layer()` helper in
`card.rs` owns the classification logic; `card_mint_key_status`
delegates to it, so the production path is under direct test with no
duplicate logic.
**Mint form always reachable.** The key panel replaces the mint form
only for `none` (first-time setup) or when the user explicitly opens the
edit panel (`editingKey`). Keys from agent/persona/process layers show
an inline provenance row on the mint form with a "Why?" affordance;
clicking it shows the read-only redirect in a panel with a Cancel button
that returns to the mint form — never a terminal state.
**Precise auth-error matching.** The 401 handling in `cardMintStore.ts`
matches `startsWith("Card mint failed (HTTP 401 ")` plus the specific
`Incorrect API key` text, so avatar-fetch 401 errors pass through
unchanged.
**Tri-state key status row.** "Using your saved OpenAI key · Update"
renders only when `keyLayer === "global"` (confirmed writable key).
Query pending or errored hides the row without asserting key existence.
**Real tests.** Panel visibility derivations live in
`cardMintKeyUtils.ts`, which `AgentCardMintDialog.tsx` imports directly.
Tests cover all layers including the mint-reachability invariant (Mint
reachable for every resolved layer; only `none` gates setup).
- `card.rs` — new `resolve_key_layer()` pure helper;
`card_mint_key_status` delegates to it; 999 lines (under the 1000-line
ratchet)
- `card/tests.rs` — precedence test calls `resolve_key_layer()` directly
(no test-local closure); adds process-layer and blank-value cases
- `tauriPersonas.ts` — `CardMintKeyLayer` type; updated
`cardMintKeyStatus` signature
- `cardMintKeyUtils.ts` — `showKeyPanel`, `showReadOnlyRow`,
`showCancelButton`, `keyPanelTitle`, and helpers; component imports all
of them
- `AgentCardMintDialog.tsx` — inline provenance rows for all key
sources; key panel only for setup/edit; no unused variables
- `cardMintStore.ts` — precise 401 prefix matching
- `e2eBridge.ts` — `card_mint_key_status` stub returns `"global"` (not
boolean)
- Tests: 3959 JS passing, 2089 Rust passing, `tsc --noEmit` clean
Related: [block/buzz#4406](https://github.com/block/buzz/pull/4406)
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1ng3jzsaqxdhrfq22dg85j3lpr0zsh3jp7g2h9jyxl59wraayapnsu6kvfg <9a232143a0336e34814a6a0f4947e11bc50bc641f21572c886fd0ae1f7a4e867@buzz.block.builderlab.xyz>
|
||
|
|
be95a8a986 |
fix(config-bridge): add harness-definition env tier and fix equal-value model override (#3580)
All seven normalized config fields resolve through sanitized
`InheritedConfigTiers` passed wholesale to `read_config_surface`. The
reader's precedence tiers now match spawn's Layer 2b exactly — including
harness-definition env — and the equal-value model-override regression
is fixed.
## Changes
**`config_bridge/types.rs`** — add `InheritedConfigTiers`: persona env,
global env, harness definition env, structured model/provider/prompt for
both tiers. Add `HarnessDefault` `ConfigOrigin` variant for
harness-definition env values.
**`commands/agent_config.rs`** — `build_inherited_tiers` now resolves
the harness definition env using the same lookup path as spawn
(`record.runtime` → `persona.runtime` → empty string) and applies
`sanitize_inherited_env` to it. `resolve_config_surface` is unchanged in
shape — tiers passed to the reader now include `definition_env`.
**`config_bridge/reader.rs`** — `env_candidates` extended to 4-element
return (record, persona, global, definition). All five field builders
that use env candidates now include the definition-env slot below global
env and above the structured block, matching spawn Layer 2b. Magic
`configured[..6]` slice replaced with `configured[..configured.len()-1]`
(named split: all non-file candidates). Equal-value model-override arm
falls through to the normal resolve path instead of early-returning
`RuntimeOverride`, so the panel shows the baseline origin (e.g.
`BuzzExplicit`) rather than a spurious "Live override" label for a no-op
switch.
**`config_bridge/reader_tests_ext.rs`** — three new Layer 2b tests:
definition env beats structured persona model, global env beats
definition env, reserved-key-absent fallthrough.
**`commands/agent_config_tests.rs`** —
`genuine_explicit_live_switch_to_same_model_yields_clean_field` updated
to assert `origin == BuzzExplicit` (not `RuntimeOverride`); wrapped in
`with_no_goose_config` for hermeticity. New
`reserved_key_in_definition_env_shaped_map_is_stripped_by_sanitize` test
pins the shared sanitization contract.
**`AgentConfigPanel.tsx` / `types.ts`** — `HarnessDefault` origin
variant wired end-to-end: TS union type and provenance sentence
("Inherited from harness definition").
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
|
||
|
|
db225c63dd |
fix(desktop): repair clippy and doc-comment issues from pass-3 corrections
- backfill.rs: remove blank line between two consecutive doc comment blocks - detach.rs: merge orphaned step-list doc comment into function doc comment - migration.rs: remove blank line after doc comment before private fn - migration_tests.rs, migration_command_tests.rs, migration_avatar_tests.rs, migration_databricks_tests.rs: add .unwrap() to calls that now return Result after C5 migration fallibility changes Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |