From be06a757a36710e2bb638af422e4b73e7641e89d Mon Sep 17 00:00:00 2001 From: Carsen Klock Date: Tue, 28 Jul 2026 20:03:47 -0600 Subject: [PATCH] feat(desktop): add Cybara ACP harness preset Signed-off-by: Carsen Klock --- desktop/public/harness-logos/CREDITS.md | 1 + desktop/public/harness-logos/cybara.png | Bin 0 -> 19697 bytes .../src-tauri/src/managed_agents/discovery.rs | 222 +----------------- .../src/managed_agents/discovery/presets.rs | 212 +++++++++++++++++ .../features/onboarding/ui/RuntimeIcon.tsx | 1 + .../onboarding/ui/presetLogos.test.mjs | 8 +- 6 files changed, 221 insertions(+), 223 deletions(-) create mode 100644 desktop/public/harness-logos/cybara.png create mode 100644 desktop/src-tauri/src/managed_agents/discovery/presets.rs diff --git a/desktop/public/harness-logos/CREDITS.md b/desktop/public/harness-logos/CREDITS.md index de4e003c3f..7a0daf90fb 100644 --- a/desktop/public/harness-logos/CREDITS.md +++ b/desktop/public/harness-logos/CREDITS.md @@ -14,6 +14,7 @@ license permits redistribution. | `omp.svg` | [can1357/oh-my-pi](https://github.com/can1357/oh-my-pi) | `667111575ebba136dadfd6989379e7f67e0d40d9` | MIT © 2025 Mario Zechner; © 2025–2026 Can Bölük | `assets/icon.svg` | None | | `kimi.png` | [MoonshotAI/kimi-cli](https://github.com/MoonshotAI/kimi-cli) | `4a550effdfcb29a25a5d325bf935296cc50cd417` | Apache-2.0; NOTICE: Kimi Code CLI © 2025 Moonshot AI | `web/public/logo.png` | None | | `grok.svg` | [SpaceXAI brand guidelines](https://x.ai/legal/brand-guidelines) | Retrieved 2026-07-25 | xAI Brand Guidelines: marks may be used to accurately refer to xAI or its services; logos must be used exactly as provided | `SpaceXAI_Grok_Assets.zip` → `Grok_Logomark_Dark.svg` | None | +| `cybara.png` | [metaspartan/cybara](https://github.com/metaspartan/cybara) | `630b38b8125c50310535b9cd567f2427f00017ea` | MIT © 2026 Carsen Klock | `cybara.png` | Resized from 689×689 to 128×128 | ## Inline SVG marks (`RUNTIME_MARKS`) diff --git a/desktop/public/harness-logos/cybara.png b/desktop/public/harness-logos/cybara.png new file mode 100644 index 0000000000000000000000000000000000000000..4f3a4e828bb2d0336003a744db73f0102ae39288 GIT binary patch literal 19697 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 Option, -} - -/// Build the catalog entry for one preset harness through an injectable -/// resolver — the seam the preset loop consumes and tests bind. -/// -/// Availability consumes only the adapter-missing arm of the builtin -/// predicate: adapter presence alone decides `Available` (exactly today's -/// behavior — an `amp-acp` without `amp` stays selectable), and -/// `underlying_cli` is consulted only when the adapter is absent, to -/// distinguish `AdapterMissing` (vendor CLI present) from `NotInstalled` -/// (neither found). See the `underlying_cli` field doc for why the full -/// `classify_runtime` predicate is deliberately not used here. -fn preset_catalog_entry( - def: &PresetHarness, - resolve: impl Fn(&str) -> Option, -) -> AcpRuntimeCatalogEntry { - let (availability, command, binary_path) = match resolve(def.command) { - Some(path) => ( - AcpAvailabilityStatus::Available, - Some(def.command.to_string()), - Some(path.display().to_string()), - ), - None => { - let underlying_cli_found = def - .underlying_cli - .map(|cli| resolve(cli).is_some()) - .unwrap_or(false); - if underlying_cli_found { - (AcpAvailabilityStatus::AdapterMissing, None, None) - } else { - (AcpAvailabilityStatus::NotInstalled, None, None) - } - } - }; - let underlying_cli_path = def - .underlying_cli - .and_then(resolve) - .map(|p| p.display().to_string()); - - let default_args = normalize_agent_args( - def.command, - def.args.iter().map(|s| s.to_string()).collect(), - ); - - AcpRuntimeCatalogEntry { - id: def.id.to_string(), - label: def.label.to_string(), - // No remote URL — all preset icons are bundled assets. - avatar_url: String::new(), - availability, - command, - binary_path, - default_args, - mcp_command: None, - model_env_var: None, - provider_env_var: None, - thinking_env_var: None, - install_hint: def.install_hint.to_string(), - install_instructions_url: def.install_instructions_url.to_string(), - can_auto_install: false, - // Kept false even for adapter presets: presets carry one flat - // install_hint (the adapter's), so the requiresExternalCli - // "CLI is missing" wording would pair the wrong noun with it. - // The builtin path, with per-availability hints, is the only - // consumer of the true case. - requires_external_cli: false, - underlying_cli_path, - node_required: false, - auth_status: AuthStatus::NotApplicable, - login_hint: None, - source: HarnessSource::Preset, - // Preset entries have static, non-editable env; definition_env is empty. - definition_env: Default::default(), - } -} - -const PRESET_HARNESSES: &[PresetHarness] = &[ - PresetHarness { - id: "cursor", - label: "Cursor", - command: "cursor-agent", - args: &["acp"], - install_instructions_url: "https://cursor.com/downloads", - install_hint: "Buzz talks to Cursor through the cursor-agent CLI's ACP mode.", - underlying_cli: None, - }, - PresetHarness { - id: "omp", - label: "Oh My Pi", - command: "omp", - args: &["acp"], - install_instructions_url: "https://github.com/can1357/oh-my-pi", - install_hint: "Buzz talks to Oh My Pi through its CLI's ACP mode (omp acp).", - underlying_cli: None, - }, - PresetHarness { - id: "grok", - label: "Grok Build", - command: "grok", - args: &["agent", "--always-approve", "stdio"], - install_instructions_url: "https://build.x.ai/docs", - install_hint: "Buzz talks to Grok Build through its CLI's agent stdio mode.", - underlying_cli: None, - }, - PresetHarness { - id: "opencode", - label: "OpenCode", - command: "opencode", - args: &["acp"], - install_instructions_url: "https://opencode.ai/docs", - install_hint: "Buzz talks to OpenCode through its CLI's ACP mode (opencode acp).", - underlying_cli: None, - }, - PresetHarness { - id: "kimi", - label: "Kimi Code", - command: "kimi", - args: &["acp"], - install_instructions_url: "https://kimi.ai/download", - install_hint: "Buzz talks to Kimi Code through its CLI's ACP mode (kimi acp).", - underlying_cli: None, - }, - PresetHarness { - id: "amp", - label: "Amp", - command: "amp-acp", - args: &[], - install_instructions_url: "https://github.com/tao12345666333/amp-acp", - install_hint: "Buzz talks to the Amp CLI through the amp-acp adapter. Follow the setup guide to install the adapter so the amp-acp command is on your PATH.", - underlying_cli: Some("amp"), - }, - PresetHarness { - id: "hermes", - label: "Hermes Agent", - command: "hermes-acp", - args: &[], - install_instructions_url: "https://hermes-agent.nousresearch.com", - install_hint: "Buzz talks to Hermes Agent through its hermes-acp command.", - underlying_cli: None, - }, - PresetHarness { - id: "openclaw", - label: "OpenClaw", - command: "openclaw", - args: &["acp"], - install_instructions_url: "https://docs.openclaw.ai/start/getting-started", - install_hint: "Buzz talks to OpenClaw through its ACP mode (openclaw acp), which relies on the OpenClaw Gateway daemon. Follow the setup guide to install both.\n\n\ - ⚠️ Execution-locus note: `openclaw acp` runs tools inside the \ - OpenClaw Gateway daemon, not in the Desktop process. \ - Desktop-injected BUZZ_* env vars are visible to the `openclaw` \ - harness process itself, but do NOT automatically reach the \ - Gateway's execution environment. If your tools or agent logic \ - needs BUZZ_* credentials at execution time, set them on the \ - Gateway's own environment separately.", - underlying_cli: None, - }, -]; - -/// Return the static preset harness definitions as `HarnessDefinition` values. -/// -/// Used by `warm_harness_registry_from_dir` to seed the loaded-harness registry -/// at startup before the frontend triggers a full discovery run. -pub(crate) fn preset_harness_definitions( -) -> Vec { - PRESET_HARNESSES - .iter() - .map( - |p| crate::managed_agents::custom_harnesses::HarnessDefinition { - id: p.id.to_string(), - label: p.label.to_string(), - command: p.command.to_string(), - args: p.args.iter().map(|s| s.to_string()).collect(), - env: std::collections::BTreeMap::new(), - install_instructions_url: p.install_instructions_url.to_string(), - install_hint: p.install_hint.to_string(), - }, - ) - .collect() -} - -/// Return the static slice of preset harness IDs. -/// -/// Used by `check_id_collision` in `custom_harnesses` to derive the reserved-ID -/// set from the single source of truth (`PRESET_HARNESSES`) rather than a -/// hand-maintained copy. Adding a preset automatically reserves its ID. -pub(crate) fn preset_harness_ids() -> &'static [&'static str] { - // `PRESET_HARNESSES` is `'static`; we project its `id` fields. - // Computed once via OnceLock to avoid repeated allocations on hot paths. - use std::sync::OnceLock; - static IDS: OnceLock> = OnceLock::new(); - IDS.get_or_init(|| PRESET_HARNESSES.iter().map(|p| p.id).collect()) - .as_slice() -} - /// Discover all ACP runtimes, optionally merging user-defined custom harnesses /// from `custom_harnesses_dir`. /// diff --git a/desktop/src-tauri/src/managed_agents/discovery/presets.rs b/desktop/src-tauri/src/managed_agents/discovery/presets.rs new file mode 100644 index 0000000000..d0a5ef3da5 --- /dev/null +++ b/desktop/src-tauri/src/managed_agents/discovery/presets.rs @@ -0,0 +1,212 @@ +use std::path::PathBuf; + +use crate::managed_agents::{ + AcpAvailabilityStatus, AcpRuntimeCatalogEntry, AuthStatus, HarnessSource, +}; + +use super::normalize_agent_args; + +pub(super) struct PresetHarness { + pub(super) id: &'static str, + pub(super) label: &'static str, + pub(super) command: &'static str, + pub(super) args: &'static [&'static str], + pub(super) install_instructions_url: &'static str, + pub(super) install_hint: &'static str, + pub(super) underlying_cli: Option<&'static str>, +} + +pub(super) fn preset_catalog_entry( + def: &PresetHarness, + resolve: impl Fn(&str) -> Option, +) -> AcpRuntimeCatalogEntry { + let (availability, command, binary_path) = match resolve(def.command) { + Some(path) => ( + AcpAvailabilityStatus::Available, + Some(def.command.to_string()), + Some(path.display().to_string()), + ), + None => { + let underlying_cli_found = def + .underlying_cli + .map(|cli| resolve(cli).is_some()) + .unwrap_or(false); + if underlying_cli_found { + (AcpAvailabilityStatus::AdapterMissing, None, None) + } else { + (AcpAvailabilityStatus::NotInstalled, None, None) + } + } + }; + let underlying_cli_path = def + .underlying_cli + .and_then(resolve) + .map(|path| path.display().to_string()); + let default_args = normalize_agent_args( + def.command, + def.args.iter().map(|arg| arg.to_string()).collect(), + ); + + AcpRuntimeCatalogEntry { + id: def.id.to_string(), + label: def.label.to_string(), + avatar_url: String::new(), + availability, + command, + binary_path, + default_args, + mcp_command: None, + model_env_var: None, + provider_env_var: None, + thinking_env_var: None, + install_hint: def.install_hint.to_string(), + install_instructions_url: def.install_instructions_url.to_string(), + can_auto_install: false, + requires_external_cli: false, + underlying_cli_path, + node_required: false, + auth_status: AuthStatus::NotApplicable, + login_hint: None, + source: HarnessSource::Preset, + definition_env: Default::default(), + } +} + +pub(super) const PRESET_HARNESSES: &[PresetHarness] = &[ + PresetHarness { + id: "cursor", + label: "Cursor", + command: "cursor-agent", + args: &["acp"], + install_instructions_url: "https://cursor.com/downloads", + install_hint: "Buzz talks to Cursor through the cursor-agent CLI's ACP mode.", + underlying_cli: None, + }, + PresetHarness { + id: "omp", + label: "Oh My Pi", + command: "omp", + args: &["acp"], + install_instructions_url: "https://github.com/can1357/oh-my-pi", + install_hint: "Buzz talks to Oh My Pi through its CLI's ACP mode (omp acp).", + underlying_cli: None, + }, + PresetHarness { + id: "grok", + label: "Grok Build", + command: "grok", + args: &["agent", "--always-approve", "stdio"], + install_instructions_url: "https://build.x.ai/docs", + install_hint: "Buzz talks to Grok Build through its CLI's agent stdio mode.", + underlying_cli: None, + }, + PresetHarness { + id: "opencode", + label: "OpenCode", + command: "opencode", + args: &["acp"], + install_instructions_url: "https://opencode.ai/docs", + install_hint: "Buzz talks to OpenCode through its CLI's ACP mode (opencode acp).", + underlying_cli: None, + }, + PresetHarness { + id: "kimi", + label: "Kimi Code", + command: "kimi", + args: &["acp"], + install_instructions_url: "https://kimi.ai/download", + install_hint: "Buzz talks to Kimi Code through its CLI's ACP mode (kimi acp).", + underlying_cli: None, + }, + PresetHarness { + id: "amp", + label: "Amp", + command: "amp-acp", + args: &[], + install_instructions_url: "https://github.com/tao12345666333/amp-acp", + install_hint: "Buzz talks to the Amp CLI through the amp-acp adapter. Follow the setup guide to install the adapter so the amp-acp command is on your PATH.", + underlying_cli: Some("amp"), + }, + PresetHarness { + id: "hermes", + label: "Hermes Agent", + command: "hermes-acp", + args: &[], + install_instructions_url: "https://hermes-agent.nousresearch.com", + install_hint: "Buzz talks to Hermes Agent through its hermes-acp command.", + underlying_cli: None, + }, + PresetHarness { + id: "openclaw", + label: "OpenClaw", + command: "openclaw", + args: &["acp"], + install_instructions_url: "https://docs.openclaw.ai/start/getting-started", + install_hint: "Buzz talks to OpenClaw through its ACP mode (openclaw acp), which relies on the OpenClaw Gateway daemon. Follow the setup guide to install both.\n\n\ + ⚠️ Execution-locus note: `openclaw acp` runs tools inside the \ + OpenClaw Gateway daemon, not in the Desktop process. \ + Desktop-injected BUZZ_* env vars are visible to the `openclaw` \ + harness process itself, but do NOT automatically reach the \ + Gateway's execution environment. If your tools or agent logic \ + needs BUZZ_* credentials at execution time, set them on the \ + Gateway's own environment separately.", + underlying_cli: None, + }, + PresetHarness { + id: "cybara", + label: "Cybara", + command: "cybara", + args: &["acp"], + install_instructions_url: "https://cybara.ai/download#cli-tui", + install_hint: "Buzz talks to Cybara through its CLI's ACP mode (cybara acp).", + underlying_cli: None, + }, +]; + +pub(crate) fn preset_harness_definitions( +) -> Vec { + PRESET_HARNESSES + .iter() + .map( + |preset| crate::managed_agents::custom_harnesses::HarnessDefinition { + id: preset.id.to_string(), + label: preset.label.to_string(), + command: preset.command.to_string(), + args: preset.args.iter().map(|arg| arg.to_string()).collect(), + env: std::collections::BTreeMap::new(), + install_instructions_url: preset.install_instructions_url.to_string(), + install_hint: preset.install_hint.to_string(), + }, + ) + .collect() +} + +pub(crate) fn preset_harness_ids() -> &'static [&'static str] { + use std::sync::OnceLock; + static IDS: OnceLock> = OnceLock::new(); + IDS.get_or_init(|| PRESET_HARNESSES.iter().map(|preset| preset.id).collect()) + .as_slice() +} + +#[cfg(test)] +mod tests { + use super::preset_harness_definitions; + + #[test] + fn cybara_launches_native_acp_mode() { + let cybara = preset_harness_definitions() + .into_iter() + .find(|preset| preset.id == "cybara") + .expect("Cybara preset should exist"); + + assert_eq!(cybara.label, "Cybara"); + assert_eq!(cybara.command, "cybara"); + assert_eq!(cybara.args, vec!["acp"]); + assert_eq!( + cybara.install_instructions_url, + "https://cybara.ai/download#cli-tui" + ); + assert!(cybara.install_hint.contains("cybara acp")); + assert!(cybara.env.is_empty()); + } +} diff --git a/desktop/src/features/onboarding/ui/RuntimeIcon.tsx b/desktop/src/features/onboarding/ui/RuntimeIcon.tsx index f9c201d115..a2f2ee0d4e 100644 --- a/desktop/src/features/onboarding/ui/RuntimeIcon.tsx +++ b/desktop/src/features/onboarding/ui/RuntimeIcon.tsx @@ -24,6 +24,7 @@ export const PRESET_LOGOS: Record = { amp: "/harness-logos/amp.png", hermes: "/harness-logos/hermes.png", openclaw: "/harness-logos/openclaw.svg", + cybara: "/harness-logos/cybara.png", }; 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 index 0ff2193155..572fa9e4b8 100644 --- a/desktop/src/features/onboarding/ui/presetLogos.test.mjs +++ b/desktop/src/features/onboarding/ui/presetLogos.test.mjs @@ -24,15 +24,15 @@ const desktopRoot = path.resolve( "../../../..", ); -const discoveryRs = readFileSync( - path.join(desktopRoot, "src-tauri/src/managed_agents/discovery.rs"), +const presetCatalogRs = readFileSync( + path.join(desktopRoot, "src-tauri/src/managed_agents/discovery/presets.rs"), "utf8", ); -const presetBlock = discoveryRs.match( +const presetBlock = presetCatalogRs.match( /const PRESET_HARNESSES: &\[PresetHarness\] = &\[([\s\S]*?)\n\];/, ); -assert.ok(presetBlock, "could not locate PRESET_HARNESSES in discovery.rs"); +assert.ok(presetBlock, "could not locate PRESET_HARNESSES in presets.rs"); const presetIds = [...presetBlock[1].matchAll(/^\s{8}id: "([^"]+)",$/gm)].map( (match) => match[1],