feat(acp): bring your own harness (BYOH) — generic ACP runtime seam + settings gallery#2773
Draft
wpfleger96 wants to merge 8 commits into
Draft
feat(acp): bring your own harness (BYOH) — generic ACP runtime seam + settings gallery#2773wpfleger96 wants to merge 8 commits into
wpfleger96 wants to merge 8 commits into
Conversation
wpfleger96
marked this pull request as draft
July 24, 2026 23:16
wpfleger96
force-pushed
the
duncan/byoh-generic-acp-harness
branch
2 times, most recently
from
July 25, 2026 02:55
bda1871 to
fa4034b
Compare
7 tasks
wpfleger96
force-pushed
the
duncan/byoh-generic-acp-harness
branch
from
July 25, 2026 05:16
fa4034b to
5ebabda
Compare
Implement a generic BYOH mechanism that lets any ACP-speaking harness (Cursor, Pi, Amp, OpenClaw, Hermes, etc.) work with Buzz without maintaining separate backend code per harness type. Backend (Rust): - Add HarnessDefinition, HarnessSource, PRESET_HARNESSES (8 presets: cursor/omp/grok/opencode/kimi/amp/hermes/openclaw) + custom harness loading from ~/.config/buzz/custom-harnesses/ - warm_harness_registry_from_dir called at startup (before restore) and transactionally on save/delete; try_record_agent_command returns typed DANGLING_HARNESS_ID:<id> error — never silently falls to default - Full descriptor resolved at spawn/readiness/spawn_hash paths; definition env merges below Buzz-reserved vars; edit round-trip carries definition_env in catalog entry - All sweep paths (reap_dead_instance_agents, kill_stale_tracked_processes, receipt cleanup) use marker/receipt ownership — no name-gating - save_custom_harness validates before mutation; atomic write + old-file delete on rename; preset ids reserved in check_id_collision - buzz_sweep_owns_process cross-platform (no #[cfg(unix)] guard) Frontend (TypeScript): - Settings > Agents BYOH gallery: preset cards (Detected/Not-installed + docs link, no Add button), custom cards (full edit/delete) - ArgsEditor (repeatable rows) + EnvEditor (KEY=VALUE pairs) - HarnessManagementCard + harnessFormLogic.ts (21 behavior tests) - harnessGalleryLogic.ts (11 tests: detected-first sort, preset filtering) - Onboarding: actionable 'More harnesses...' button → Settings > Agents - PRESET_LOGOS bundled map; avatarUrl stripped from all surfaces Tests: +27 frontend / +20 Rust lib (registry lifecycle, env round-trip, sweep ownership, legacy-JSON serde, preset collision) Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96
force-pushed
the
duncan/byoh-generic-acp-harness
branch
from
July 25, 2026 05:20
5ebabda to
277d8b0
Compare
…safe registry
B-4 (persistence contract): Replace the two simulated round-trip tests that
called raw fs::write/remove_file with six tests that exercise the real
save_custom_harness_to_dir helper directly:
- 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 boundary): Six tests exercising validate_harness_definition_pub
integration with validate_user_env_keys for the documented attack surfaces:
- validate_rejects_malformed_key_with_equals_sign (BUZZ_AUTH_TAG=x forgery shape)
- validate_rejects_reserved_key_buzz_managed_agent (ownership marker)
- validate_rejects_reserved_key_case_insensitive (lowercase bypass)
- validate_rejects_nul_byte_in_value (Command::env panic protection)
- validate_rejects_value_over_per_value_size_limit
- validate_accepts_well_formed_env
B-2 (API boundary): Export fromRawAcpRuntimeCatalogEntry from tauri.ts and
add four tests to tauri.test.mjs proving the Rust definition_env snake_case
field is mapped to definitionEnv camelCase, that absent definition_env defaults
to {} (not undefined), and that env round-trips end-to-end through the mapper
so a save-then-edit cycle cannot erase definition env.
B-6 (concurrent-safe registry): Add save_and_warm + delete_and_warm functions
in custom_harnesses.rs that hold a PERSIST_MUTEX for the filesystem mutation
and the subsequent warm_harness_registry_from_dir call as an atomic unit.
Update save_custom_harness and delete_custom_harness Tauri commands to use
these helpers. Eliminates the lost-update window where two concurrent saves
could interleave their warm calls and produce a stale registry snapshot.
File-size override added for custom_harnesses.rs (1042 lines after the new
tests; queued to split once the feature stabilizes).
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
D-12: Revert mobile/pubspec.lock to origin/main. The meta 1.17→1.18 /
test 1.30→1.31 churn was a no-source-change lockfile bump introduced by
accident; reverting eliminates the unrelated diff.
D-15: Add OpenClaw execution-locus note to the preset's install_hint.
Eva's finding: openclaw acp executes tools inside the Gateway daemon, not
in the Desktop process. Desktop-injected BUZZ_* env vars reach the
openclaw harness process itself but do NOT automatically propagate to
the Gateway's execution environment. The install_hint now surfaces this
caveat so users who need BUZZ_* credentials at execution time know they
must set them on the Gateway's own environment.
F8 (onboarding navigate test): New pure-logic test file
postOnboardingNav.test.mjs proves the App.tsx postOnboardingNav
useEffect predicate — navigateAfterComplete does not fire before
machine.stage reaches 'ready', fires exactly once on the ready
transition, is cleared after firing (no double-fire), fires immediately
if nav arrives while already ready, and carries the exact
{to: '/settings', search: {section: 'agents'}} shape from
MachineOnboardingFlow's navigateToAgentSettings action.
C-10 (e2eBridge + handler tests): Extract save_custom_harness /
delete_custom_harness handler logic into e2eBridgeCustomHarnesses.ts
(exported module-level functions + mockCustomHarnesses Map +
resetMockCustomHarnesses). Wire the handlers into e2eBridge.ts:
- mockCustomHarnesses imported and appended to discover_acp_providers
results so saved custom harnesses appear in future discovery calls
- case 'save_custom_harness' → handleSaveCustomHarness
- case 'delete_custom_harness' → handleDeleteCustomHarness
New test file e2eBridgeCustomHarnesses.test.mjs (14 tests across 3
describe groups) proves: save → store, non-empty env preserved,
empty env absent, same-ID edit replaces, rename removes old + inserts
new, delete removes, delete idempotent, Map reference is shared.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
… delete-error knob
C-10 (full form — Playwright spec):
Add desktop/tests/e2e/harness-management.spec.ts with 10 tests covering:
- Preset gallery: detected badge present for available preset
- Preset gallery: install link shown, no badge for not-installed preset
- Add custom harness: form save → row appears in list
- Edit preserves env vars: definitionEnv round-trip via edit form
- Same-ID edit replaces row without duplication
- Rename: old row gone, new row present
- Delete success: row disappears
- Delete failure: inline error shown, row kept (error-injection knob)
- PATH badge on custom row: Detected badge when availability === 'available'
- F8 onboarding navigate: setup-page More-harnesses click →
completes onboarding → Settings → Agents (settings-harness-management)
This exercises the real App.tsx effect that gates router.navigate()
on machine.stage === 'ready', which postOnboardingNav.test.mjs cannot
cover (it simulates the predicate, not the render path).
Delete-error injection knob:
- Add deleteCustomHarnessError?: string to MockBridgeOptions in bridge.ts
- Add same field to MockE2eConfig in e2eBridge.ts
- Thread config through to handleDeleteCustomHarness so the e2e bridge
throws when the knob is set (mirrors acpAuthMethodsError pattern)
- Add unit test: throws when knob set + entry survives in store
D-15 (README execution-locus sentence, full placement):
The install_hint placement (already in discovery.rs) is correct and stays.
Add the missing sentence to the README blockquote at line 265 per the brief:
Desktop-injected BUZZ_* env vars do NOT reach the execution locus unless
set on the Gateway's own environment.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96
pushed a commit
that referenced
this pull request
Jul 25, 2026
Member
Author
PRESET_HARNESSES ships 8 tier-2 presets; PRESET_LOGOS and desktop/public/harness-logos/ covered only 6. `hermes` and `openclaw` fell through to the generic TerminalSquare glyph beside six siblings showing real brand marks, and RuntimeIcon's onError fallback made a missing file indistinguishable from an unmapped id at runtime. Add both marks from their MIT-licensed upstreams: - hermes.png from NousResearch/hermes-agent@6ad632b website/static/img/logo.png — cropped the baked-in border frame (a dark box once downscaled), padded square, 64x64, 16-colour palette. 1.7 KB, in line with the existing 1.0-3.4 KB siblings. - openclaw.svg from openclaw/openclaw@b06f40a ui/public/favicon.svg — upstream animates the mascot with SMIL specifically so it plays inside <img>-loaded SVGs, which is exactly how RuntimeIcon renders it. Stripped the animation elements to hold the rest pose (verified pixel-identical to the upstream t=0 frame at 256px) so a settings list doesn't bob and blink. Provenance and modifications are recorded in a new CREDITS.md next to the assets, and the contributor guide's step 4 now requires that row. The guard: the two sides of this mapping are a Rust slice and a TS record, so nothing typechecks them against each other — which is why the gap went unnoticed through review and screenshots. presetLogos.test.mjs parses PRESET_HARNESSES out of discovery.rs and asserts every preset id has a PRESET_LOGOS entry whose file exists on disk, plus the reverse direction for stale entries. It follows the cross-language fixture precedent in effortTable.fixture.test.mjs. Verified failing on each of the three drift modes it claims to catch, not just passing green. Importing RuntimeIcon.tsx from a test needed the node test loader to tolerate Vite asset imports (`./claude.png?inline`), which it resolved as a bare package and threw on; assets now load as an inert stub, the same treatment CSS already gets. Co-authored-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com> Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
Brings the branch up to date with origin/main (2a051a4) so CI and local gates run against the tree that will actually merge. Requested as a merge commit rather than a rebase to keep Duncan's authored history intact. Two ratchet entries in desktop/scripts/check-file-sizes.mjs needed composing, since both sides raised per-file ceilings from a shared base: - src-tauri/src/commands/agent_discovery.rs — textual conflict. BYOH took 1826 -> 2038 (+212); main #2767 took 1826 -> 1836 (+10). Resolved to 2048 with both comment blocks kept, preserving the slack each side intended rather than discarding one growth. - src-tauri/src/managed_agents/discovery.rs — no textual conflict, but the BYOH ceiling of 1715 predates main's +17 and the merged file is 1729 lines, so the check failed on a clean auto-merge. Composed to 1732. The overrides map is a ceiling (violation only when lineCount > limit), so composing deltas is the resolution that matches the file's convention. Verified on the merged tree: desktop file-size check clean, frontend suite 3554/3555 (the one failure is the pre-existing styled-qr-code 'Cannot find package qrcode' local module-resolution fault, which fails identically on pristine heads), presetLogos drift guard 10/10 with all 8 preset ids still parsing out of main's modified discovery.rs, tsc --noEmit clean, biome clean over 1640 files, just test-unit 5/5 crates, and desktop-tauri-test 1691/1691. Co-authored-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com> Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
|
Windows end-to-end proof completed against PR #2773 frozen at Proof shape:
Two Windows Hermes terminal issues were exposed by the proof:
One Buzz-side safety finding: a blank/new managed-agent parallelism value inherited This validates the generic ACP harness path on Windows; it does not imply a production routing cutover. |
The C-10 harness-management spec was 5 passed / 5 failed and had never run in
CI — it was added without an entry in either playwright.config.ts project's
testMatch, so no gate covered it. Three test-side defects, no product defects.
1. Ambiguous "Save" selector. getByRole("button", {name: "Save"}) substring-
matches "Save defaults" from AgentDefaultsEditor, a sibling card in the
same Settings -> Agents panel. Scoped to the form, exact match.
2. The mock catalog ignored its own mutation store. handleDiscoverAcpRuntimes
returned a test-supplied acpRuntimesCatalog early, never merging
mockCustomHarnesses — so save/delete mutated a store the seeded specs
never read back, and add/edit/rename/delete all silently no-oped.
Fixed with one mergeMockCustomHarnesses used by both catalog paths. A
deleted *seeded* row has no store entry to remove, so removal needs a
tombstone set rather than just the map; the same mechanism covers the id
vacated by a rename.
3. The onboarding-nav test was unsatisfiable as written. It needs machine
onboarding to run (community must not vouch for the active identity) and
needsSetup to be false afterwards (or App.tsx renders WelcomeSetup instead
of the router). skipCommunitySeed leaves zero communities and fails the
second; the default seed stamps the active pubkey and fails the first.
Seeding a community under a foreign pubkey satisfies both.
Instrumenting this showed the navigation itself always worked — the hash
became #/settings?section=agents on click, with WelcomeSetup painted over
it because the router never mounted. The route intent is correct.
Adds unit coverage for the merge/tombstone logic; reverting the tombstone
filter fails exactly the delete and rename cases.
Verified at 91080e0: harness-management 10/10, unit suite 3560/3561 (the
one failure is the pre-existing styled-qr-code / missing qrcode package
fault, identical on a pristine tree), tsc clean, biome clean 1641 files,
check-file-sizes clean.
Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
Replace the incorrect Oh My Posh and Dify marks with the exact upstream Oh My Pi and Kimi Code assets. Use xAI’s official Grok logomark under its nominative brand terms, with presentation-only backgrounds for marks that need contrast. Remove the unproven Cursor bitmap and deliberately retain the generic terminal fallback because Cursor publishes assets without granting redistribution permission. Record exact provenance and keep the drift guard explicit about that exception. Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Collaborator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.








What
Implements a "bring your own harness" (BYOH) generic ACP mechanism — replacing per-harness backend code with a data-driven 3-tier system:
PRESET_HARNESSES, no code duplication, icons stay TerminalSquare/bundled-asset-onlycustom_harnesses/under app data; managed via Settings → Agents UIChanges
Core data model
HarnessDefinition— id, label, command, args, env, install URL/hintPRESET_HARNESSESstatic 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 entryPersistence (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 wherefs::renameover an existing file is "access denied"save_and_warm/delete_and_warm— holdPERSIST_MUTEXfor 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 snapshotEnv validation boundary (B-3)
validate_harness_definition_pubcallsvalidate_user_env_keyson definition env at save AND loadTypeScript boundary (B-2 / Thufir CRITICAL)
RawAcpRuntimeCatalogEntrynow declaresdefinition_env?: Record<string,string>andsource: "builtin" | "preset" | "custom"fromRawAcpRuntimeCatalogEntrymapsdefinition_env → definitionEnv(camelCase); absent field defaults to{}entry.definitionEnv— env no longer erased on save-then-edit cycleUnified descriptor (Phase A / Thufir F4)
EffectiveHarnessDescriptor { command, args, env }inreadiness.rsresolve_effective_harness_descriptor()— single resolver used by spawn, spawn_hash, summary, get_agent_models (both saved and unsaved), and readinessOther fixes
runtime.defaultArgsintorecord.agent_argson normal create pathsMissingBinaryvariant for custom commands not found on PATHsetTimeout(0)removed, parent-owned route intent vianavigateAfterCompletepropHarnessManagementCardusesharnessGalleryLogichelpers (killed duplicate filter/sort)BUILTIN_IDSderived fromPRESET_HARNESSES(no hand-maintained copy)mobile/pubspec.lockchurn revertedinstallInstructionsUrlTests 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_valuesB-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_envB-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 shapePreset catalog
cursorcursorampamphermeshermes-agentopenclawopenclawpipi-agentcodestoryaideGate table — head
f7c55505ecargo test --libjust desktop-testjust desktop-typecheckjust desktop-checkjust desktop-tauri-clippyjust desktop-buildPR head:
f7c55505e66f8f8634d997f54c022f3fbe68e417— MERGEABLE