From 22218cc034f6aad5d02fdaf25470f77b092a525a Mon Sep 17 00:00:00 2001
From: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
Date: Sat, 25 Jul 2026 09:03:46 -0400
Subject: [PATCH] feat(desktop): ship Hermes + OpenClaw tier-2 logos with a
coverage guard
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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
-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>
---
crates/buzz-acp/README.md | 2 +-
desktop/public/harness-logos/CREDITS.md | 16 ++++
desktop/public/harness-logos/hermes.png | Bin 0 -> 1698 bytes
desktop/public/harness-logos/openclaw.svg | 16 ++++
.../features/onboarding/ui/RuntimeIcon.tsx | 4 +-
.../onboarding/ui/presetLogos.test.mjs | 75 ++++++++++++++++++
desktop/test-loader-hooks.mjs | 21 +++++
7 files changed, 132 insertions(+), 2 deletions(-)
create mode 100644 desktop/public/harness-logos/CREDITS.md
create mode 100644 desktop/public/harness-logos/hermes.png
create mode 100644 desktop/public/harness-logos/openclaw.svg
create mode 100644 desktop/src/features/onboarding/ui/presetLogos.test.mjs
diff --git a/crates/buzz-acp/README.md b/crates/buzz-acp/README.md
index db38c32ae..957b0d824 100644
--- a/crates/buzz-acp/README.md
+++ b/crates/buzz-acp/README.md
@@ -306,7 +306,7 @@ To add a new runtime to the tier-2 gallery:
1. **Verify the ACP entrypoint** from the vendor's own documentation — do not rely on a PR description alone. Test with the actual binary.
2. **Add a `HarnessDefinition` entry** to the `PRESET_HARNESSES` slice in `desktop/src-tauri/src/managed_agents/discovery.rs`. Fill `id`, `label`, `command`, `args`, `install_instructions_url`, `install_hint`. Leave `env` empty unless the harness requires a specific env var to enable ACP mode.
3. **Add the preset id to `BUILTIN_IDS`** in `desktop/src-tauri/src/managed_agents/custom_harnesses.rs` so custom JSON files cannot shadow it.
-4. **Add a bundled logo** (64×64 PNG or optimised SVG) to `desktop/public/harness-logos/.png` and add a corresponding entry to `PRESET_LOGOS` in `desktop/src/features/onboarding/ui/RuntimeIcon.tsx`.
+4. **Add a bundled logo** (64×64 PNG or optimised SVG) to `desktop/public/harness-logos/.png` and add a corresponding entry to `PRESET_LOGOS` in `desktop/src/features/onboarding/ui/RuntimeIcon.tsx`. Record the source and license in `desktop/public/harness-logos/CREDITS.md`. Only bundle a mark whose upstream license permits redistribution; skipping this step is caught by `presetLogos.test.mjs`, which asserts every `PRESET_HARNESSES` id has a mapped logo that exists on disk.
5. Run `cargo test --lib` and `just desktop-typecheck` to verify everything compiles.
The built-in `BUILTIN_IDS` set (`goose`, `claude`, `codex`, `buzz-agent`, and all current preset ids) is the reserved namespace; every other id is available for custom harnesses.
diff --git a/desktop/public/harness-logos/CREDITS.md b/desktop/public/harness-logos/CREDITS.md
new file mode 100644
index 000000000..f3a53a13b
--- /dev/null
+++ b/desktop/public/harness-logos/CREDITS.md
@@ -0,0 +1,16 @@
+# Preset harness logos — provenance
+
+Third-party marks bundled to identify tier-2 preset harnesses in the runtime
+gallery (`PRESET_LOGOS` in `desktop/src/features/onboarding/ui/RuntimeIcon.tsx`).
+Nominative use only — each mark identifies its own vendor's harness.
+
+Add a row here when adding a preset logo; only bundle marks whose upstream
+license permits redistribution.
+
+| File | Upstream | Commit | License | Source path | Modifications |
+|---|---|---|---|---|---|
+| `hermes.png` | [NousResearch/hermes-agent](https://github.com/NousResearch/hermes-agent) | `6ad632b` | MIT © 2025 Nous Research | `website/static/img/logo.png` | Cropped the baked-in border frame, padded to square, resized to 64×64, quantised to a 16-colour palette |
+| `openclaw.svg` | [openclaw/openclaw](https://github.com/openclaw/openclaw) | `b06f40a` | MIT © 2026 OpenClaw Foundation | `ui/public/favicon.svg` | Removed the SMIL animation elements (renders the upstream rest pose statically — verified pixel-identical to the upstream frame at t=0); minified paths |
+
+`amp.png`, `cursor.png`, `grok.png`, `kimi.png`, `omp.png`, and `opencode.svg`
+predate this file; their provenance was not recorded when they were added.
diff --git a/desktop/public/harness-logos/hermes.png b/desktop/public/harness-logos/hermes.png
new file mode 100644
index 0000000000000000000000000000000000000000..6acf0020b1d2d756db6353f086f030f936e96ed6
GIT binary patch
literal 1698
zcmV;T23`4yP)^Yin~&CQaMl4E0IKtMnn8yf}&1_J{F0RaI4000000RR91%-`68000Ip
zNklluFyw9mh>&G3Y>*i!62qf{r92H8XK;X)cKpK`
zryXjiPG#&A>vWjbKAdV%3aC|3B%KhghS1mmB4phgH&_Qq*aU(SNcQX|frNeB{*eu#
zkMrmGeeU=7z27;%b3`#COGvsq<`KPT3)j8nAn0?01RVAM0O1L(Fp9lSv~92MSN{tL
zx4lfFCaojkJzGkQLe|oK^~>_1
z>+1Sb6T2`c*G}3m?@pmB(p*e&60v(|Rso)Z5{y@j=(*0X5u0%#UgV|B7|je$yG2t0&(Kd1rM^
z@aB8Y3@f-jU6_M=+E+Kdt&Q$I@W%^%vwQ#aqNj9MQ3hx!JIb~hqc?aL_9(qs)|Rxk
zZhg;;&maB^(Ys+@!J-G&RNk(txITXUoX+-lygvw5escZJ;6TQtt7OWyaA~L&a#ti#pT9cGCW^yn*?&&_PzV4t-0DRj+PPs>y
z$?QnRWS+O*0%Vqd$X3J5fjv)EQ#5rrNf|FEnZj4}H=7iMBXjaRDASsEH)-v^Ro68!*kn_>e+3z;|ChywqJ5NH4e0
z0UQFiWX5E#gvEOrvYUD;;UABfR;U^@6SR*1*W?SWYm*K+vA4j?m8
zfA#zKCO)x#{K@YWJ^8hfJvYDb-8cLC7uGYjuL00-X73)T_>o};TIJpfm4a|vv>p^>
zw`V;3YX5Gp|1Z&=xP1
z-kzrYMqd+)7vH?)G8Uaw&@F)UxMT
zz+O;J%F$9*>9!FCbH=710697qkVa3Z8nnBB^!Al~UjXDZ*zXQ~oZr5
zB9zLEOjPqpGz7#5s|l|;08-A04Amv+Q4%!Vh%541K}TR{JDH9E4nS751WlZuCWTp8
z7K;L+Mar}d*dXylN@8f93Qe)Z|GfypFc8x9{yG~X0*R@NRNTEC)Q!!T(lJZeEOykb)X${%>K6oih(O+?&sVMMH%!b;82#mrTY{*&PC
z=XO#W0Ub?ZuW~VZ%l=)B5sPO?)s)BW-ow)7~?i=0Ql{d^>6
z`o=;F3*hQvFkRxttXHbX$Q$}{NVRNQ>ss_dNhYb@rAZTVf8scIDxFu8j^j9q!Cj$H
z(s3NeaS{VhojX15CD(nTs^a3f1_h8j*7@saweZlP6Ly90uT*-$tp}{-Xc0`|?36
z{_P~9i%5Nu7aBkQW_j29?haQhT$?+oXGyIBKC17XaOlIJQ|1c?lD5xQR~NTXY*jgq
z^UK#lj*~b&=7k^gq~jbqdgs-n2`5=sHK6N8*SW8{>kj_80G#XHQwnMyy!LU2>%`aL
z%LCJuG<3`59Om%_z`(e-jz88pv9SoqD*WR08o$cC
sn+DTOpvSW(Fz?E^NE|@u4}GrS-xEWL^_Of4r2qf`07*qoM6N<$g1-(-FaQ7m
literal 0
HcmV?d00001
diff --git a/desktop/public/harness-logos/openclaw.svg b/desktop/public/harness-logos/openclaw.svg
new file mode 100644
index 000000000..8c1496d62
--- /dev/null
+++ b/desktop/public/harness-logos/openclaw.svg
@@ -0,0 +1,16 @@
+
diff --git a/desktop/src/features/onboarding/ui/RuntimeIcon.tsx b/desktop/src/features/onboarding/ui/RuntimeIcon.tsx
index 11b4be8a8..3cee0be9a 100644
--- a/desktop/src/features/onboarding/ui/RuntimeIcon.tsx
+++ b/desktop/src/features/onboarding/ui/RuntimeIcon.tsx
@@ -18,13 +18,15 @@ const RUNTIME_LOGOS: Record = {
// Public-path logos for bundled presets. Served from /harness-logos/ at runtime.
// Keys match the preset `id` values emitted by the backend PRESET_HARNESSES.
-const PRESET_LOGOS: Record = {
+export const PRESET_LOGOS: Record = {
cursor: "/harness-logos/cursor.png",
omp: "/harness-logos/omp.png",
grok: "/harness-logos/grok.png",
opencode: "/harness-logos/opencode.svg",
kimi: "/harness-logos/kimi.png",
amp: "/harness-logos/amp.png",
+ hermes: "/harness-logos/hermes.png",
+ openclaw: "/harness-logos/openclaw.svg",
};
function isBuzzRuntime(runtime: AcpRuntimeCatalogEntry): boolean {
diff --git a/desktop/src/features/onboarding/ui/presetLogos.test.mjs b/desktop/src/features/onboarding/ui/presetLogos.test.mjs
new file mode 100644
index 000000000..81b22ec8b
--- /dev/null
+++ b/desktop/src/features/onboarding/ui/presetLogos.test.mjs
@@ -0,0 +1,75 @@
+/**
+ * Preset-logo coverage guard.
+ *
+ * Every tier-2 preset the backend emits must have a bundled logo, or it renders
+ * as the generic TerminalSquare fallback next to siblings that show real marks.
+ * The two sides live in different languages — Rust `PRESET_HARNESSES` vs the TS
+ * `PRESET_LOGOS` record — so no compiler catches drift, and `RuntimeIcon`'s
+ * `onError` fallback hides a missing file at runtime. This test reads the Rust
+ * source as text (the same trick `motion.test.mjs` uses for CSS) and asserts
+ * both directions plus on-disk existence of every mapped file.
+ */
+
+import assert from "node:assert/strict";
+import { existsSync, readFileSync } from "node:fs";
+import path from "node:path";
+import test from "node:test";
+import { fileURLToPath } from "node:url";
+
+import { PRESET_LOGOS } from "./RuntimeIcon.tsx";
+
+const desktopRoot = path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ "../../../..",
+);
+
+const discoveryRs = readFileSync(
+ path.join(desktopRoot, "src-tauri/src/managed_agents/discovery.rs"),
+ "utf8",
+);
+
+const presetBlock = discoveryRs.match(
+ /const PRESET_HARNESSES: &\[PresetHarness\] = &\[([\s\S]*?)\n\];/,
+);
+assert.ok(presetBlock, "could not locate PRESET_HARNESSES in discovery.rs");
+
+const presetIds = [...presetBlock[1].matchAll(/^\s{8}id: "([^"]+)",$/gm)].map(
+ (match) => match[1],
+);
+
+test("PRESET_HARNESSES parse found the preset ids", () => {
+ // Guards the regex itself: a struct-field rename would otherwise silently
+ // yield zero ids and make every assertion below vacuously pass.
+ assert.ok(
+ presetIds.length >= 8,
+ `expected at least 8 preset ids, parsed ${presetIds.length}`,
+ );
+});
+
+for (const id of presetIds) {
+ test(`preset "${id}" has a bundled logo`, () => {
+ const logoPath = PRESET_LOGOS[id];
+ assert.ok(
+ logoPath,
+ `preset "${id}" has no PRESET_LOGOS entry — it renders the generic ` +
+ `TerminalSquare fallback. Add desktop/public${logoPath ?? `/harness-logos/${id}.png`} ` +
+ `and map it in RuntimeIcon.tsx.`,
+ );
+ assert.ok(
+ existsSync(path.join(desktopRoot, "public", logoPath)),
+ `PRESET_LOGOS["${id}"] points at ${logoPath}, which is missing from ` +
+ `desktop/public — RuntimeIcon's onError would silently fall back.`,
+ );
+ });
+}
+
+test("PRESET_LOGOS has no entries for unknown presets", () => {
+ const unknown = Object.keys(PRESET_LOGOS).filter(
+ (id) => !presetIds.includes(id),
+ );
+ assert.deepEqual(
+ unknown,
+ [],
+ `PRESET_LOGOS maps ids the backend does not emit as presets: ${unknown.join(", ")}`,
+ );
+});
diff --git a/desktop/test-loader-hooks.mjs b/desktop/test-loader-hooks.mjs
index c71cc1859..dd6f34327 100644
--- a/desktop/test-loader-hooks.mjs
+++ b/desktop/test-loader-hooks.mjs
@@ -54,7 +54,20 @@ const stubModules = new Map([
const STUB_URL_PREFIX = "buzz-test-stub:";
+// Vite resolves asset imports (`./logo.png`, `./logo.png?inline`) to a URL or
+// base64 string at bundle time; node's ESM resolver has no such loader and
+// throws on the query suffix. Serve an inert string so components that embed
+// assets stay unit-testable.
+const ASSET_SPECIFIER = /\.(?:png|jpe?g|gif|svg|webp|avif|ico)(?:\?[^/]*)?$/;
+const ASSET_URL_PREFIX = "buzz-test-asset:";
+
export function resolve(specifier, context, nextResolve) {
+ if (ASSET_SPECIFIER.test(specifier)) {
+ return {
+ shortCircuit: true,
+ url: `${ASSET_URL_PREFIX}${specifier}`,
+ };
+ }
if (stubModules.has(specifier)) {
return {
shortCircuit: true,
@@ -98,6 +111,14 @@ export function resolve(specifier, context, nextResolve) {
}
export async function load(url, context, nextLoad) {
+ if (url.startsWith(ASSET_URL_PREFIX)) {
+ return {
+ format: "module",
+ shortCircuit: true,
+ source: 'export default "test-asset";\n',
+ };
+ }
+
if (url.startsWith(STUB_URL_PREFIX)) {
return {
format: "module",