diff --git a/README.md b/README.md index 72af92ce138..0f187e4d9bb 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ Agents are part of the room, not haunted cron jobs. |---|---|---| | Relay, channels, threads, DMs, canvases, media, search, audit log | Mobile clients (iOS + Android, Flutter) | Web-of-trust reputation across relays | | Desktop app (Tauri + React) | Workflow approval gates (infra exists, glue still drying) | Push notifications | -| `buzz-cli` (agent-first, JSON in / JSON out) + ACP harness (Goose, Codex, Claude Code) | Huddle lifecycle events | Culture features | +| `buzz-cli` (agent-first, JSON in / JSON out) + ACP harness (Goose, Codex, Claude Code, Oh My Pi) | Huddle lifecycle events | Culture features | | YAML workflows: message / reaction / schedule / webhook triggers | | | | Git events (NIP-34: patches, repo announcements, status) | | | | Git hosting backend | | | @@ -206,7 +206,7 @@ A Rust workspace of focused crates. Single source of truth: the relay. See [ARCH **Services** — `buzz-db` (Postgres) · `buzz-auth` (NIP-42/98 Schnorr auth, rate limiting) · `buzz-pubsub` (Redis, presence, typing) · `buzz-search` (Postgres FTS) · `buzz-audit` (hash-chain log). Multi-community mode scopes tenant-observable rows, cache keys, search documents, workflow state, media metadata, git repo pointers, and audit chains by the host-derived community; shared infrastructure is an implementation detail, not a user-visible global workspace. -**Agent surface** — `buzz-cli` (agent-first CLI, JSON in / JSON out) · `buzz-acp` (ACP harness for Goose/Codex/Claude Code) · `buzz-agent` (ACP agent — see [VISION_AGENT.md](VISION_AGENT.md)) · `buzz-dev-mcp` (shell + file-edit tools) · `buzz-workflow` (YAML automation) · `buzz-persona` (agent persona packs) +**Agent surface** — `buzz-cli` (agent-first CLI, JSON in / JSON out) · `buzz-acp` (ACP harness for Goose/Codex/Claude Code/Oh My Pi) · `buzz-agent` (ACP agent — see [VISION_AGENT.md](VISION_AGENT.md)) · `buzz-dev-mcp` (shell + file-edit tools) · `buzz-workflow` (YAML automation) · `buzz-persona` (agent persona packs) **Git & pairing** — `git-sign-nostr` / `git-credential-nostr` (nostr-signed git) · `buzz-pair-relay` / `buzz-pairing-cli` (relay pairing) diff --git a/crates/buzz-acp/README.md b/crates/buzz-acp/README.md index e6164b02dd3..83b9c4eab3d 100644 --- a/crates/buzz-acp/README.md +++ b/crates/buzz-acp/README.md @@ -9,7 +9,7 @@ Buzz Relay ──WS──→ buzz-acp ──stdio──→ Your Agent (send_message, etc.) ``` -Supports any agent that speaks [ACP](https://agentclientprotocol.com/) over stdio: **goose**, **codex** (via [codex-acp](https://github.com/agentclientprotocol/codex-acp)), and **claude code** (via [claude-agent-acp](https://github.com/agentclientprotocol/claude-agent-acp)). +Supports any agent that speaks [ACP](https://agentclientprotocol.com/) over stdio: **goose**, **codex** (via [codex-acp](https://github.com/agentclientprotocol/codex-acp)), **claude code** (via [claude-agent-acp](https://github.com/agentclientprotocol/claude-agent-acp)), and **oh my pi** (via `omp acp` — [omp.sh](https://omp.sh/)). ## Prerequisites @@ -98,6 +98,25 @@ buzz-acp Older installs that still expose `claude-code-acp` are also supported. `buzz-acp` treats both Claude ACP command names as the same zero-arg runtime. +## Running with Oh My Pi + +[Oh My Pi](https://omp.sh/) (`omp`) is a single-binary, multi-provider CLI whose ACP server is the `omp acp` subcommand — no separate adapter. It resolves its own model and credentials from `~/.omp`, so you authenticate omp once (e.g. an Anthropic Max subscription) and Buzz just spawns it. + +```bash +# Install omp (see https://omp.sh/) +curl -fsSL https://omp.sh/install.sh | bash + +# Authenticate a provider once (interactive), e.g. Anthropic Max: +# omp # then run /login and pick Anthropic + +# Run +export BUZZ_ACP_AGENT_COMMAND="omp" # args default to `acp` + +buzz-acp +``` + +Select a model with `--model` in `BUZZ_ACP_AGENT_ARGS` (e.g. `acp,--model,claude-opus-4-5`) or set a default in `~/.omp/agent/config.yml`. + ## Configuration All configuration is via environment variables (or CLI flags — every env var has a matching flag). @@ -269,7 +288,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, 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. Oh My Pi is additionally surfaced in the first-run onboarding picker alongside the tier-1 harnesses (`ONBOARDING_RUNTIME_ORDER` in `desktop/src/features/onboarding/ui/onboardingRuntimeSelection.ts`). > **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. diff --git a/crates/buzz-acp/src/config.rs b/crates/buzz-acp/src/config.rs index a38d6faa14b..7a9f2b32fcc 100644 --- a/crates/buzz-acp/src/config.rs +++ b/crates/buzz-acp/src/config.rs @@ -616,7 +616,8 @@ pub(crate) fn normalize_agent_command_identity(command: &str) -> String { fn default_agent_args(command: &str) -> Option> { match normalize_agent_command_identity(command).as_str() { - "goose" => Some(vec!["acp".to_string()]), + // Single-binary agents whose ACP server is a subcommand of the CLI. + "goose" | "omp" | "oh-my-pi" => Some(vec!["acp".to_string()]), "codex" | "codex-acp" | "claude-agent-acp" | "claude-code-acp" | "claude-code" | "claudecode" | "buzz-agent" => Some(Vec::new()), _ => None, @@ -1440,6 +1441,19 @@ mod tests { assert_eq!(normalize_agent_args("goose", vec!["".into()]), vec!["acp"]); } + #[test] + fn normalizes_omp_args_to_acp() { + // omp exposes its ACP server as `omp acp`, so args default to ["acp"]. + assert_eq!(normalize_agent_args("omp", Vec::new()), vec!["acp"]); + assert_eq!(normalize_agent_args("omp", vec!["".into()]), vec!["acp"]); + assert_eq!(normalize_agent_args("omp", vec!["acp".into()]), vec!["acp"]); + assert_eq!( + normalize_agent_args("/usr/local/bin/omp", Vec::new()), + vec!["acp"] + ); + assert_eq!(normalize_agent_args("Oh My Pi", Vec::new()), vec!["acp"]); + } + #[test] fn normalizes_codex_and_claude_args_to_empty() { assert_eq!( @@ -1495,6 +1509,12 @@ mod tests { #[test] fn normalize_agent_command_identity_variants() { assert_eq!(normalize_agent_command_identity("goose"), "goose"); + assert_eq!(normalize_agent_command_identity("omp"), "omp"); + assert_eq!( + normalize_agent_command_identity("/Users/me/.bun/bin/omp"), + "omp" + ); + assert_eq!(normalize_agent_command_identity("Oh My Pi"), "oh-my-pi"); assert_eq!( normalize_agent_command_identity("C:\\Program Files\\Goose\\goose.exe"), "goose" diff --git a/desktop/src-tauri/src/managed_agents/discovery.rs b/desktop/src-tauri/src/managed_agents/discovery.rs index 2b7264b4295..bfdb442c408 100644 --- a/desktop/src-tauri/src/managed_agents/discovery.rs +++ b/desktop/src-tauri/src/managed_agents/discovery.rs @@ -1527,8 +1527,8 @@ const PRESET_HARNESSES: &[PresetHarness] = &[ label: "Oh My Pi", command: "omp", args: &["acp"], - install_instructions_url: "https://github.com/can1357/oh-my-pi", - install_hint: "Install Oh My Pi from github.com/can1357/oh-my-pi.", + install_instructions_url: "https://omp.sh/", + install_hint: "Install Oh My Pi from omp.sh (curl -fsSL https://omp.sh/install.sh | bash).", underlying_cli: None, }, PresetHarness { diff --git a/desktop/src/features/onboarding/ui/onboardingRuntimeSelection.test.mjs b/desktop/src/features/onboarding/ui/onboardingRuntimeSelection.test.mjs index 221702ebb2a..d3c5982e740 100644 --- a/desktop/src/features/onboarding/ui/onboardingRuntimeSelection.test.mjs +++ b/desktop/src/features/onboarding/ui/onboardingRuntimeSelection.test.mjs @@ -17,6 +17,7 @@ test("all bundled harnesses are visible in onboarding", () => { assert.equal(runtimeIsVisibleInOnboarding("codex"), true); assert.equal(runtimeIsVisibleInOnboarding("goose"), true); assert.equal(runtimeIsVisibleInOnboarding("buzz-agent"), true); + assert.equal(runtimeIsVisibleInOnboarding("omp"), true); assert.equal(runtimeIsVisibleInOnboarding("custom"), false); }); @@ -24,13 +25,14 @@ test("visible onboarding runtimes use the product order", () => { const runtimes = [ runtime("buzz-agent", "available", "not_applicable"), runtime("codex", "available", "logged_in"), + runtime("omp", "available", "not_applicable"), runtime("goose", "available", "not_applicable"), runtime("claude", "available", "logged_in"), ]; assert.deepEqual( getVisibleOnboardingRuntimes(runtimes).map(({ id }) => id), - ["claude", "codex", "goose", "buzz-agent"], + ["claude", "codex", "goose", "buzz-agent", "omp"], ); }); @@ -60,12 +62,13 @@ test("ready onboarding runtimes exclude unknown and non-ready harnesses", () => runtime("goose", "available", "not_applicable"), runtime("codex", "available", "logged_out"), runtime("buzz-agent", "available", "not_applicable"), + runtime("omp", "available", "not_applicable"), runtime("claude", "available", "logged_in"), runtime("custom", "available", "not_applicable"), ]; assert.deepEqual( getReadyOnboardingRuntimes(runtimes).map(({ id }) => id), - ["claude", "goose", "buzz-agent"], + ["claude", "goose", "buzz-agent", "omp"], ); }); diff --git a/desktop/src/features/onboarding/ui/onboardingRuntimeSelection.ts b/desktop/src/features/onboarding/ui/onboardingRuntimeSelection.ts index cd491dfcc5a..981988150e2 100644 --- a/desktop/src/features/onboarding/ui/onboardingRuntimeSelection.ts +++ b/desktop/src/features/onboarding/ui/onboardingRuntimeSelection.ts @@ -5,6 +5,7 @@ export const ONBOARDING_RUNTIME_ORDER = [ "codex", "goose", "buzz-agent", + "omp", ]; const VISIBLE_ONBOARDING_RUNTIME_IDS = new Set(