mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
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:
@@ -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.
|
||||
|
||||
@@ -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 |
@@ -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(", ")}`,
|
||||
);
|
||||
});
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user