diff --git a/desktop/public/harness-logos/CREDITS.md b/desktop/public/harness-logos/CREDITS.md
index de4e003c3f..716c43e1ae 100644
--- a/desktop/public/harness-logos/CREDITS.md
+++ b/desktop/public/harness-logos/CREDITS.md
@@ -9,6 +9,7 @@ license permits redistribution.
| File | Upstream | Commit | License | Source path | Modifications |
|---|---|---|---|---|---|
+| `devin.svg` | [Cognition Devin documentation](https://docs.devin.ai/cli) | Retrieved 2026-07-27 | Cognition trademark; nominative use to identify the Devin harness | Official documentation `logo/favicon.svg` | Added the official black mark to a white square canvas so it remains legible in both app themes |
| `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 |
| `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 |
diff --git a/desktop/public/harness-logos/devin.svg b/desktop/public/harness-logos/devin.svg
new file mode 100644
index 0000000000..e760797d68
--- /dev/null
+++ b/desktop/public/harness-logos/devin.svg
@@ -0,0 +1,4 @@
+
diff --git a/desktop/src-tauri/src/managed_agents/discovery.rs b/desktop/src-tauri/src/managed_agents/discovery.rs
index eecbf4de3e..7190816fd0 100644
--- a/desktop/src-tauri/src/managed_agents/discovery.rs
+++ b/desktop/src-tauri/src/managed_agents/discovery.rs
@@ -10,8 +10,11 @@ use crate::managed_agents::{
HarnessSource,
};
+mod presets;
mod runtime_metadata;
+use presets::{preset_catalog_entry, PRESET_HARNESSES};
+pub(crate) use presets::{preset_harness_definitions, preset_harness_ids};
pub(crate) use runtime_metadata::KnownAcpRuntime;
const GOOSE_AVATAR_URL: &str = "https://goose-docs.ai/img/logo_dark.png";
@@ -1436,225 +1439,6 @@ pub(crate) fn discover_acp_runtime_availability(runtime_id: &str) -> 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..72c4657dc7
--- /dev/null
+++ b/desktop/src-tauri/src/managed_agents/discovery/presets.rs
@@ -0,0 +1,335 @@
+use std::path::PathBuf;
+use std::sync::OnceLock;
+
+use crate::managed_agents::{
+ AcpAvailabilityStatus, AcpRuntimeCatalogEntry, AuthStatus, HarnessSource,
+};
+
+use super::normalize_agent_args;
+
+/// Static data for a well-known tier-2 ACP harness.
+pub(super) struct PresetHarness {
+ pub(super) id: &'static str,
+ label: &'static str,
+ command: &'static str,
+ args: &'static [&'static str],
+ install_instructions_url: &'static str,
+ install_hint: &'static str,
+ /// Vendor CLI the ACP command wraps, when the preset is an adapter.
+ ///
+ /// Consulted only when the adapter is absent, so `AdapterMissing` replaces
+ /// `NotInstalled` when the CLI is present but the adapter is not. `None`
+ /// when the command is itself the vendor CLI.
+ underlying_cli: Option<&'static str>,
+}
+
+/// Build one preset catalog entry through an injectable command resolver.
+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());
+
+ 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: normalize_agent_args(
+ def.command,
+ def.args.iter().map(|arg| arg.to_string()).collect(),
+ ),
+ 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,
+ // Presets carry one flat install hint, so builtin external-CLI copy
+ // would name the wrong missing component for adapter presets.
+ 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: "devin",
+ label: "Devin",
+ command: "devin",
+ args: &["acp"],
+ install_instructions_url: "https://docs.devin.ai/cli",
+ install_hint: "Buzz talks to Devin through the official Devin CLI's ACP mode (devin acp).",
+ underlying_cli: None,
+ },
+ 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 preset definitions for the spawn/readiness registry.
+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: Default::default(),
+ install_instructions_url: preset.install_instructions_url.to_string(),
+ install_hint: preset.install_hint.to_string(),
+ },
+ )
+ .collect()
+}
+
+/// Return preset IDs from the catalog's single source of truth.
+pub(crate) fn preset_harness_ids() -> &'static [&'static str] {
+ static IDS: OnceLock> = OnceLock::new();
+ IDS.get_or_init(|| PRESET_HARNESSES.iter().map(|preset| preset.id).collect())
+ .as_slice()
+}
+
+#[cfg(test)]
+mod tests {
+ use std::path::PathBuf;
+
+ use crate::managed_agents::{AcpAvailabilityStatus, AuthStatus, HarnessSource};
+
+ use super::{preset_catalog_entry, PresetHarness, PRESET_HARNESSES};
+
+ /// Amp-shaped preset: an ACP adapter wrapping a separately installed CLI.
+ const ADAPTER_PRESET: PresetHarness = PresetHarness {
+ id: "amp-test",
+ label: "Amp Test",
+ command: "amp-acp",
+ args: &[],
+ install_instructions_url: "https://example.com/install",
+ install_hint: "Install the amp-acp npm adapter.",
+ underlying_cli: Some("amp"),
+ };
+
+ #[test]
+ fn devin_preset_uses_official_native_acp_invocation() {
+ let preset = PRESET_HARNESSES
+ .iter()
+ .find(|preset| preset.id == "devin")
+ .expect("Devin preset should be present");
+
+ assert_eq!(preset.label, "Devin");
+ assert_eq!(preset.command, "devin");
+ assert_eq!(preset.args, &["acp"]);
+ assert_eq!(preset.underlying_cli, None);
+ assert_eq!(preset.install_instructions_url, "https://docs.devin.ai/cli");
+
+ let entry = preset_catalog_entry(preset, |command| {
+ (command == "devin").then(|| PathBuf::from("/usr/local/bin/devin"))
+ });
+ assert_eq!(entry.availability, AcpAvailabilityStatus::Available);
+ assert_eq!(entry.command.as_deref(), Some("devin"));
+ assert_eq!(entry.default_args, vec!["acp"]);
+ assert_eq!(entry.binary_path.as_deref(), Some("/usr/local/bin/devin"));
+ assert_eq!(entry.auth_status, AuthStatus::NotApplicable);
+ assert_eq!(entry.source, HarnessSource::Preset);
+
+ let missing_entry = preset_catalog_entry(preset, |_| None);
+ assert_eq!(
+ missing_entry.availability,
+ AcpAvailabilityStatus::NotInstalled
+ );
+ assert!(missing_entry.command.is_none());
+ assert_eq!(missing_entry.default_args, vec!["acp"]);
+ }
+
+ #[test]
+ fn devin_preset_is_exposed_in_the_runtime_catalog() {
+ use crate::managed_agents::custom_harnesses::registry_test_lock;
+
+ // Discovery touches process-global command-resolution and the loaded
+ // harness registry. Serialize with the other discovery tests.
+ let _path_guard = crate::managed_agents::lock_path_mutex();
+ let _registry_guard = registry_test_lock();
+
+ let entry = super::super::discover_acp_runtimes_from(None)
+ .into_iter()
+ .find(|entry| entry.id == "devin")
+ .expect("Devin preset should appear in the runtime catalog");
+
+ assert_eq!(entry.label, "Devin");
+ assert_eq!(entry.default_args, vec!["acp"]);
+ assert_eq!(entry.install_instructions_url, "https://docs.devin.ai/cli");
+ assert_eq!(entry.source, HarnessSource::Preset);
+ }
+
+ #[test]
+ fn adapter_missing_when_underlying_cli_present() {
+ let entry = preset_catalog_entry(&ADAPTER_PRESET, |command| {
+ (command == "amp").then(|| PathBuf::from("/usr/local/bin/amp"))
+ });
+ assert_eq!(entry.availability, AcpAvailabilityStatus::AdapterMissing);
+ assert!(entry.command.is_none());
+ assert!(entry.binary_path.is_none());
+ assert_eq!(
+ entry.underlying_cli_path.as_deref(),
+ Some("/usr/local/bin/amp")
+ );
+ assert!(!entry.requires_external_cli);
+ assert_eq!(entry.install_hint, "Install the amp-acp npm adapter.");
+ }
+
+ #[test]
+ fn not_installed_when_adapter_and_cli_are_missing() {
+ let entry = preset_catalog_entry(&ADAPTER_PRESET, |_| None);
+ assert_eq!(entry.availability, AcpAvailabilityStatus::NotInstalled);
+ assert!(entry.underlying_cli_path.is_none());
+ assert!(!entry.requires_external_cli);
+ }
+
+ #[test]
+ fn available_when_adapter_and_cli_are_present() {
+ let entry = preset_catalog_entry(&ADAPTER_PRESET, |command| match command {
+ "amp-acp" => Some(PathBuf::from("/usr/local/bin/amp-acp")),
+ "amp" => Some(PathBuf::from("/usr/local/bin/amp")),
+ _ => None,
+ });
+ assert_eq!(entry.availability, AcpAvailabilityStatus::Available);
+ assert_eq!(entry.command.as_deref(), Some("amp-acp"));
+ assert_eq!(entry.binary_path.as_deref(), Some("/usr/local/bin/amp-acp"));
+ assert_eq!(
+ entry.underlying_cli_path.as_deref(),
+ Some("/usr/local/bin/amp")
+ );
+ }
+
+ #[test]
+ fn adapter_presence_is_enough_for_availability() {
+ let entry = preset_catalog_entry(&ADAPTER_PRESET, |command| {
+ (command == "amp-acp").then(|| PathBuf::from("/usr/local/bin/amp-acp"))
+ });
+ assert_eq!(entry.availability, AcpAvailabilityStatus::Available);
+ assert_eq!(entry.command.as_deref(), Some("amp-acp"));
+ assert_eq!(entry.binary_path.as_deref(), Some("/usr/local/bin/amp-acp"));
+ assert!(entry.underlying_cli_path.is_none());
+ }
+
+ #[test]
+ fn preset_without_underlying_cli_stays_simple() {
+ let preset = PresetHarness {
+ underlying_cli: None,
+ ..ADAPTER_PRESET
+ };
+ let entry = preset_catalog_entry(&preset, |_| None);
+ assert_eq!(entry.availability, AcpAvailabilityStatus::NotInstalled);
+ assert!(!entry.requires_external_cli);
+ assert!(entry.underlying_cli_path.is_none());
+ }
+}
diff --git a/desktop/src-tauri/src/managed_agents/discovery/tests.rs b/desktop/src-tauri/src/managed_agents/discovery/tests.rs
index 1b587dca0e..6fe6a77521 100644
--- a/desktop/src-tauri/src/managed_agents/discovery/tests.rs
+++ b/desktop/src-tauri/src/managed_agents/discovery/tests.rs
@@ -6,9 +6,9 @@ use super::{
codex_adapter_is_outdated, create_time_agent_command_override, default_agent_command,
effective_agent_command, find_nvm_default_bin, find_via_login_shell,
is_login_shell_path_uninit, is_safe_nvm_tag, managed_agent_avatar_url, normalize_agent_args,
- parse_semver_tag, preset_catalog_entry, probe_codex_acp_version, record_agent_command,
- refresh_login_shell_path, try_record_agent_command, PresetHarness, BUZZ_AGENT_AVATAR_URL,
- CLAUDE_CODE_AVATAR_URL, CODEX_AVATAR_URL, GOOSE_AVATAR_URL,
+ parse_semver_tag, probe_codex_acp_version, record_agent_command, refresh_login_shell_path,
+ try_record_agent_command, BUZZ_AGENT_AVATAR_URL, CLAUDE_CODE_AVATAR_URL, CODEX_AVATAR_URL,
+ GOOSE_AVATAR_URL,
};
use crate::managed_agents::AcpAvailabilityStatus;
@@ -187,90 +187,6 @@ fn classifies_cli_missing_when_adapter_found_but_cli_absent() {
assert_eq!(path.as_deref(), Some("/opt/homebrew/bin/codex-acp"));
}
-/// Amp-shaped preset: an ACP adapter (`amp-acp`) wrapping a separately
-/// installed vendor CLI (`amp`).
-const ADAPTER_PRESET: PresetHarness = PresetHarness {
- id: "amp-test",
- label: "Amp Test",
- command: "amp-acp",
- args: &[],
- install_instructions_url: "https://example.com/install",
- install_hint: "Install the amp-acp npm adapter.",
- underlying_cli: Some("amp"),
-};
-
-#[test]
-fn preset_entry_adapter_missing_when_underlying_cli_present() {
- // Vendor CLI resolves, adapter does not — the state Tyler's Amp
- // hand-test hit. Must NOT degrade to the misleading NotInstalled.
- let entry = preset_catalog_entry(&ADAPTER_PRESET, |cmd| {
- (cmd == "amp").then(|| PathBuf::from("/usr/local/bin/amp"))
- });
- assert_eq!(entry.availability, AcpAvailabilityStatus::AdapterMissing);
- assert!(entry.command.is_none());
- assert!(entry.binary_path.is_none());
- assert_eq!(
- entry.underlying_cli_path.as_deref(),
- Some("/usr/local/bin/amp")
- );
- assert!(!entry.requires_external_cli);
- assert_eq!(entry.install_hint, "Install the amp-acp npm adapter.");
-}
-
-#[test]
-fn preset_entry_not_installed_when_both_missing() {
- let entry = preset_catalog_entry(&ADAPTER_PRESET, |_| None);
- assert_eq!(entry.availability, AcpAvailabilityStatus::NotInstalled);
- assert!(entry.underlying_cli_path.is_none());
- assert!(!entry.requires_external_cli);
-}
-
-#[test]
-fn preset_entry_available_when_adapter_and_cli_present() {
- let entry = preset_catalog_entry(&ADAPTER_PRESET, |cmd| match cmd {
- "amp-acp" => Some(PathBuf::from("/usr/local/bin/amp-acp")),
- "amp" => Some(PathBuf::from("/usr/local/bin/amp")),
- _ => None,
- });
- assert_eq!(entry.availability, AcpAvailabilityStatus::Available);
- assert_eq!(entry.command.as_deref(), Some("amp-acp"));
- assert_eq!(entry.binary_path.as_deref(), Some("/usr/local/bin/amp-acp"));
- assert_eq!(
- entry.underlying_cli_path.as_deref(),
- Some("/usr/local/bin/amp")
- );
-}
-
-#[test]
-fn preset_entry_stays_available_when_adapter_present_but_cli_absent() {
- // Wren's regression guard: today an `amp-acp` install without `amp`
- // is Available and selectable. Feeding underlying_cli through the
- // FULL classify_runtime predicate would flip this to CliMissing
- // (unselectable, with backwards install copy) — the adapter-missing
- // arm is the only one presets consume.
- let entry = preset_catalog_entry(&ADAPTER_PRESET, |cmd| {
- (cmd == "amp-acp").then(|| PathBuf::from("/usr/local/bin/amp-acp"))
- });
- assert_eq!(entry.availability, AcpAvailabilityStatus::Available);
- assert_eq!(entry.command.as_deref(), Some("amp-acp"));
- assert_eq!(entry.binary_path.as_deref(), Some("/usr/local/bin/amp-acp"));
- assert!(entry.underlying_cli_path.is_none());
-}
-
-#[test]
-fn preset_entry_without_underlying_cli_stays_simple() {
- // Most presets: the command IS the vendor CLI. No external-CLI flag,
- // absent command means plain NotInstalled.
- let preset = PresetHarness {
- underlying_cli: None,
- ..ADAPTER_PRESET
- };
- let entry = preset_catalog_entry(&preset, |_| None);
- assert_eq!(entry.availability, AcpAvailabilityStatus::NotInstalled);
- assert!(!entry.requires_external_cli);
- assert!(entry.underlying_cli_path.is_none());
-}
-
fn persona_with_runtime(id: &str, runtime: Option<&str>) -> crate::managed_agents::AgentDefinition {
crate::managed_agents::AgentDefinition {
id: id.to_string(),
diff --git a/desktop/src/features/onboarding/ui/RuntimeIcon.tsx b/desktop/src/features/onboarding/ui/RuntimeIcon.tsx
index f9c201d115..5b247c31f7 100644
--- a/desktop/src/features/onboarding/ui/RuntimeIcon.tsx
+++ b/desktop/src/features/onboarding/ui/RuntimeIcon.tsx
@@ -17,6 +17,7 @@ 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.
export const PRESET_LOGOS: Record = {
+ devin: "/harness-logos/devin.svg",
omp: "/harness-logos/omp.svg",
grok: "/harness-logos/grok.svg",
opencode: "/harness-logos/opencode.svg",
diff --git a/desktop/src/features/onboarding/ui/presetLogos.test.mjs b/desktop/src/features/onboarding/ui/presetLogos.test.mjs
index 0ff2193155..7a5df620da 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 presetsRs = readFileSync(
+ path.join(desktopRoot, "src-tauri/src/managed_agents/discovery/presets.rs"),
"utf8",
);
-const presetBlock = discoveryRs.match(
+const presetBlock = presetsRs.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],