feat(desktop): ship Hermes + OpenClaw tier-2 logos with a coverage guard

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>
This commit is contained in:
tlongwell-block
2026-07-25 09:03:46 -04:00
parent 0922869abe
commit 22218cc034
7 changed files with 132 additions and 2 deletions
+1 -1
View File
@@ -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/<id>.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/<id>.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.
+16
View File
@@ -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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

+16
View File
@@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" fill="none">
<defs>
<linearGradient id="a" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#ff4d4d"/>
<stop offset="100%" stop-color="#991b1b"/>
</linearGradient>
</defs>
<path d="M60 10C30 10 15 35 15 55c0 20 15 40 30 45v10h10v-10s5 2 10 0v10h10v-10c15-5 30-25 30-45 0-20-15-45-45-45Z" fill="url(#a)"/>
<path d="M20 45C5 40 0 50 5 60s15 5 20-5c3-7 0-10-5-10Z" fill="url(#a)"/>
<path d="M100 45c15-5 20 5 15 15s-15 5-20-5c-3-7 0-10 5-10Z" fill="url(#a)"/>
<path d="M45 15Q35 5 30 8M75 15Q85 5 90 8" stroke="#ff4d4d" stroke-width="3" stroke-linecap="round"/>
<circle cx="45" cy="35" r="6" fill="#050810"/>
<circle cx="75" cy="35" r="6" fill="#050810"/>
<circle cx="46" cy="34" r="2.5" fill="#00e5cc"/>
<circle cx="76" cy="34" r="2.5" fill="#00e5cc"/>
</svg>

After

Width:  |  Height:  |  Size: 878 B

@@ -18,13 +18,15 @@ const RUNTIME_LOGOS: Record<string, string> = {
// 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<string, string> = {
export const PRESET_LOGOS: Record<string, string> = {
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 {
@@ -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(", ")}`,
);
});
+21
View File
@@ -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",