diff --git a/crates/buzz-acp/README.md b/crates/buzz-acp/README.md index cf36111a936..41d9a214bdd 100644 --- a/crates/buzz-acp/README.md +++ b/crates/buzz-acp/README.md @@ -283,7 +283,7 @@ Buzz Desktop supports registering any ACP-speaking agent tool as a selectable ru **Tier-1 — compiled-in runtimes** (Goose, Claude Code, Codex, Buzz Agent): have auto-installers, auth probes, and first-class onboarding. Their IDs (`goose`, `claude`, `codex`, `buzz-agent`) are reserved and cannot be overridden. -**Tier-2 — preset catalog** (Cursor, Oh My Pi, Grok Build, OpenCode, Kimi Code, Amp, Hermes Agent, OpenClaw): static `HarnessDefinition` entries in `desktop/src-tauri/src/managed_agents/discovery.rs` (`PRESET_HARNESSES`). They are always present in the runtime catalog, PATH-probed for availability, not editable or deletable by the user. Displayed with bundled logos; if not installed, a docs link appears instead. +**Tier-2 — preset catalog** (Cursor, Oh My Pi, Pi, Grok Build, OpenCode, Kimi Code, Amp, Hermes Agent, OpenClaw): static `HarnessDefinition` entries in `desktop/src-tauri/src/managed_agents/discovery/presets.rs` (`PRESET_HARNESSES`). They are always present in the runtime catalog, PATH-probed for availability, not editable or deletable by the user. Displayed with bundled logos; if not installed, a docs link appears instead. > **Note — OpenClaw:** `openclaw acp` is a Gateway-backed bridge; PATH availability shows "Available" even when the OpenClaw Gateway daemon is not running. This is expected tier-2 semantics (same class as a preset with unconfigured auth). The Gateway URL is configured via `OPENCLAW_GATEWAY_URL` (or the equivalent env var from OpenClaw's docs) — set it in the agent's **env vars** in Edit Agent, not in the definition env (the preset definition carries no env entries). Note that `openclaw acp` executes tools inside the Gateway daemon, not the Desktop process, so Desktop-injected `BUZZ_*` env vars do NOT reach the execution locus unless you also set them on the Gateway's own environment. @@ -327,10 +327,9 @@ Invalid files (bad JSON, unknown id, empty command) are skipped with a warning a 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`. 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. +2. **Add a `PresetHarness` entry** to the `PRESET_HARNESSES` slice in `desktop/src-tauri/src/managed_agents/discovery/presets.rs`. Fill `id`, `label`, `command`, `args`, `install_instructions_url`, `install_hint`, and `underlying_cli` when the command wraps a separately installed CLI. Preset ids are automatically reserved so custom JSON files cannot shadow them. +3. **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. +4. 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 index 716c43e1ae3..dee5aa257e9 100644 --- a/desktop/public/harness-logos/CREDITS.md +++ b/desktop/public/harness-logos/CREDITS.md @@ -13,6 +13,7 @@ license permits redistribution. | `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 | +| `pi.svg` | [earendil-works/pi-website](https://github.com/earendil-works/pi-website) | `2f5e410b97474d0a34ec2500aa1aa58d6c3f992c` | MIT © 2026 Earendil Inc. and contributors | `src/favicon.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 | diff --git a/desktop/public/harness-logos/pi.svg b/desktop/public/harness-logos/pi.svg new file mode 100644 index 00000000000..c28d6242332 --- /dev/null +++ b/desktop/public/harness-logos/pi.svg @@ -0,0 +1,21 @@ + + + + + + diff --git a/desktop/src-tauri/src/managed_agents/discovery/presets.rs b/desktop/src-tauri/src/managed_agents/discovery/presets.rs index fd853094515..61c589770ca 100644 --- a/desktop/src-tauri/src/managed_agents/discovery/presets.rs +++ b/desktop/src-tauri/src/managed_agents/discovery/presets.rs @@ -90,6 +90,15 @@ pub(super) fn preset_catalog_entry( } pub(super) const PRESET_HARNESSES: &[PresetHarness] = &[ + PresetHarness { + id: "pi", + label: "Pi", + command: "pi-acp", + args: &[], + install_instructions_url: "https://github.com/svkozak/pi-acp", + install_hint: "Buzz talks to Pi through the pi-acp adapter. Install Pi with `npm install -g --ignore-scripts @earendil-works/pi-coding-agent`, then install the adapter with `npm install -g pi-acp`.", + underlying_cli: Some("pi"), + }, PresetHarness { id: "devin", label: "Devin", @@ -347,6 +356,48 @@ mod tests { assert_eq!(entry.source, HarnessSource::Preset); } + #[test] + fn pi_preset_uses_zero_arg_adapter_and_reports_missing_component() { + let preset = PRESET_HARNESSES + .iter() + .find(|preset| preset.id == "pi") + .expect("Pi preset should be present"); + + assert_eq!(preset.label, "Pi"); + assert_eq!(preset.command, "pi-acp"); + assert!(preset.args.is_empty()); + assert_eq!(preset.underlying_cli, Some("pi")); + + let available = preset_catalog_entry(preset, |command| match command { + "pi-acp" => Some(PathBuf::from("/usr/local/bin/pi-acp")), + "pi" => Some(PathBuf::from("/usr/local/bin/pi")), + _ => None, + }); + assert_eq!(available.availability, AcpAvailabilityStatus::Available); + assert_eq!(available.command.as_deref(), Some("pi-acp")); + assert!(available.default_args.is_empty()); + assert_eq!( + available.underlying_cli_path.as_deref(), + Some("/usr/local/bin/pi") + ); + + let adapter_missing = preset_catalog_entry(preset, |command| { + (command == "pi").then(|| PathBuf::from("/usr/local/bin/pi")) + }); + assert_eq!( + adapter_missing.availability, + AcpAvailabilityStatus::AdapterMissing + ); + assert!(adapter_missing.command.is_none()); + assert!(adapter_missing.default_args.is_empty()); + + let not_installed = preset_catalog_entry(preset, |_| None); + assert_eq!( + not_installed.availability, + AcpAvailabilityStatus::NotInstalled + ); + } + #[test] fn adapter_missing_when_underlying_cli_present() { let entry = preset_catalog_entry(&ADAPTER_PRESET, |command| { diff --git a/desktop/src/features/onboarding/ui/RuntimeIcon.tsx b/desktop/src/features/onboarding/ui/RuntimeIcon.tsx index 5b247c31f73..57f1a703d1e 100644 --- a/desktop/src/features/onboarding/ui/RuntimeIcon.tsx +++ b/desktop/src/features/onboarding/ui/RuntimeIcon.tsx @@ -19,6 +19,7 @@ const RUNTIME_LOGOS: Record = { export const PRESET_LOGOS: Record = { devin: "/harness-logos/devin.svg", omp: "/harness-logos/omp.svg", + pi: "/harness-logos/pi.svg", grok: "/harness-logos/grok.svg", opencode: "/harness-logos/opencode.svg", kimi: "/harness-logos/kimi.png", diff --git a/desktop/src/features/settings/ui/harnessCatalogCopy.test.mjs b/desktop/src/features/settings/ui/harnessCatalogCopy.test.mjs new file mode 100644 index 00000000000..86a3f6b760f --- /dev/null +++ b/desktop/src/features/settings/ui/harnessCatalogCopy.test.mjs @@ -0,0 +1,11 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { harnessDescription } from "./harnessCatalogCopy.ts"; + +test("Pi catalog entry has its curated product description", () => { + assert.equal( + harnessDescription("pi"), + "A minimal terminal coding harness, connected through the pi-acp adapter.", + ); +}); diff --git a/desktop/src/features/settings/ui/harnessCatalogCopy.ts b/desktop/src/features/settings/ui/harnessCatalogCopy.ts index 9a71ff70f92..f91dada83fc 100644 --- a/desktop/src/features/settings/ui/harnessCatalogCopy.ts +++ b/desktop/src/features/settings/ui/harnessCatalogCopy.ts @@ -27,6 +27,8 @@ const HARNESS_DESCRIPTIONS: Record = { cursor: "Cursor's coding agent, connected to Buzz through its ACP server.", // Source: https://github.com/can1357/oh-my-pi omp: "A terminal coding agent with integrated development tools.", + // Sources: https://pi.dev/docs/latest, https://github.com/svkozak/pi-acp + pi: "A minimal terminal coding harness, connected through the pi-acp adapter.", // Source: https://build.x.ai (docs unavailable during research; kept // deliberately conservative). grok: "xAI's coding agent, connected to Buzz through its ACP entrypoint.",