Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, Grok Build) | Huddle lifecycle events | Culture features |
| YAML workflows: message / reaction / schedule / webhook triggers | | |
| Git events (NIP-34: patches, repo announcements, status) | | |
| Git hosting backend | | |
Expand Down Expand Up @@ -174,7 +174,7 @@ If you'd rather point buzz at a different bash-compatible shell, set `BUZZ_SHELL
┌─────────────────────────────────────────────────────────────────────────┐
│ Clients │
│ Human client AI agent CLI / scripts │
│ (Buzz desktop) (Goose, Codex, ...) (buzz-cli, agents) │
│ (Buzz desktop) (Goose, Codex, Grok, ...) (buzz-cli, agents) │
│ │ ┌──────────────┐ │ │
│ │ │ buzz-acp │ │ │
│ │ │ (ACP ↔ MCP) │ │ │
Expand Down Expand Up @@ -204,7 +204,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/Grok Build) · `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)

Expand Down
4 changes: 2 additions & 2 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ header fallback. There is no REST API for fetching message threads — use

## ACP Harness (optional, end-to-end with a real agent)

`buzz-acp` connects an ACP-speaking agent (goose, codex, claude code,
`buzz-acp` connects an ACP-speaking agent (goose, codex, claude code, Grok Build, Grok Build,
buzz-agent) to the relay. The harness listens for events, drives the
agent over stdio, and the agent replies through MCP tools.

Expand Down Expand Up @@ -222,7 +222,7 @@ buzz-acp # foreground; logs to stdout (run in

> **Using a different ACP agent?** The default recipe assumes `goose` is on
> `$PATH` and configured (`goose --version` should print). For codex / claude
> code / buzz-agent, set `BUZZ_ACP_AGENT_COMMAND` and `BUZZ_ACP_AGENT_ARGS`
> code / Grok Build / buzz-agent, set `BUZZ_ACP_AGENT_COMMAND` and `BUZZ_ACP_AGENT_ARGS`
> accordingly — see `crates/buzz-acp/README.md`. Without these, buzz-acp
> will fail to spawn the agent subprocess on startup.

Expand Down
19 changes: 18 additions & 1 deletion crates/buzz-acp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 **Grok Build** (native `grok agent stdio`).

## Prerequisites

Expand Down Expand Up @@ -71,6 +71,23 @@ buzz-acp

> **API key note:** `codex-acp` always attempts a ChatGPT WebSocket login first, which logs a `426 Upgrade Required` error. This is expected and non-fatal — it falls back to `OPENAI_API_KEY` automatically. Set `OPENAI_API_KEY` to ensure it has a working fallback.

## Running with Grok Build

[Grok Build](https://x.ai/) speaks ACP natively — no separate adapter package.

```bash
# Install: curl -fsSL https://x.ai/cli/install.sh | bash
# Auth: grok login OR export XAI_API_KEY=...

export BUZZ_ACP_AGENT_COMMAND="grok"
# Default args are: agent,--always-approve,stdio
export BUZZ_ACP_AGENT_ARGS="agent,--always-approve,stdio"

buzz-acp
```

Grok discovers skills under `~/.grok/skills` and project `.agents/skills` (including Buzz's generated `buzz-cli` skill).

## Running with Claude Code

[claude-agent-acp](https://github.com/agentclientprotocol/claude-agent-acp) wraps the Claude Agent SDK in an ACP interface.
Expand Down
31 changes: 27 additions & 4 deletions crates/buzz-acp/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,14 @@ pub(crate) fn normalize_agent_command_identity(command: &str) -> String {
fn default_agent_args(command: &str) -> Option<Vec<String>> {
match normalize_agent_command_identity(command).as_str() {
"goose" => Some(vec!["acp".to_string()]),
// Grok Build speaks ACP natively over stdio.
// `grok agent --always-approve stdio` keeps managed turns unattended.
"grok" | "grok-build" | "grok-acp" => Some(vec![
"agent".to_string(),
"--always-approve".to_string(),
"stdio".to_string(),
]),

"codex" | "codex-acp" | "claude-agent-acp" | "claude-code-acp" | "claude-code"
| "claudecode" | "buzz-agent" => Some(Vec::new()),
_ => None,
Expand Down Expand Up @@ -686,10 +694,10 @@ pub fn normalize_agent_args(command: &str, agent_args: Vec<String>) -> Vec<Strin
}

// Older callers relied on the Goose-specific default even for runtimes like
// Codex and Claude. Treat that legacy fallback as "no args" for zero-arg
// providers so desktop- and env-based launches behave the same way.
if normalized.len() == 1 && normalized[0].eq_ignore_ascii_case("acp") && default_args.is_empty()
{
// Codex, Claude, and Grok. Treat a bare `acp` as "use the runtime default"
// so desktop- and env-based launches behave the same way (empty for zero-arg
// adapters; `agent stdio` for Grok Build).
if normalized.len() == 1 && normalized[0].eq_ignore_ascii_case("acp") {
return default_args;
}

Expand Down Expand Up @@ -1432,6 +1440,21 @@ mod tests {
assert_eq!(normalize_agent_args("goose", vec!["".into()]), vec!["acp"]);
}

#[test]
fn normalizes_grok_args_to_agent_stdio() {
let expected = vec![
"agent".to_string(),
"--always-approve".to_string(),
"stdio".to_string(),
];
assert_eq!(normalize_agent_args("grok", Vec::new()), expected);
assert_eq!(normalize_agent_args("grok", vec!["acp".into()]), expected);
assert_eq!(
normalize_agent_args("/Users/me/.grok/bin/grok", Vec::new()),
expected
);
}

#[test]
fn normalizes_codex_and_claude_args_to_empty() {
assert_eq!(
Expand Down
53 changes: 51 additions & 2 deletions desktop/src-tauri/src/managed_agents/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub(crate) use runtime_metadata::KnownAcpRuntime;
const GOOSE_AVATAR_URL: &str = "https://goose-docs.ai/img/logo_dark.png";
const CLAUDE_CODE_AVATAR_URL: &str = "https://anthropic.gallerycdn.vsassets.io/extensions/anthropic/claude-code/2.1.77/1773707456892/Microsoft.VisualStudio.Services.Icons.Default";
const CODEX_AVATAR_URL: &str = "https://openai.gallerycdn.vsassets.io/extensions/openai/chatgpt/26.5313.41514/1773706730621/Microsoft.VisualStudio.Services.Icons.Default";
const GROK_AVATAR_URL: &str = "https://grok.com/favicon.ico";
const BUZZ_AGENT_AVATAR_URL: &str =
"https://raw.githubusercontent.com/block/buzz/refs/heads/main/crates/buzz-agent/buzz-agent.png";

Expand All @@ -38,6 +39,8 @@ fn common_binary_paths() -> &'static [PathBuf] {
paths.extend([
home.join(".local/share/mise/shims"),
home.join(".local/bin"),
// Grok Build installer places the CLI at ~/.grok/bin/grok.
home.join(".grok/bin"),
home.join(".volta/bin"),
home.join(".asdf/shims"),
]);
Expand Down Expand Up @@ -157,6 +160,42 @@ const KNOWN_ACP_RUNTIMES: &[KnownAcpRuntime] = &[
// Verified: `codex login status` exits 0 when logged in, non-zero otherwise.
auth_probe_args: Some(&["codex", "login", "status"]),
},
KnownAcpRuntime {
id: "grok",
label: "Grok Build",
// Native ACP via `grok agent stdio` (no separate *-acp adapter package).
commands: &["grok"],
aliases: &["grok-build", "grok-acp"],
avatar_url: GROK_AVATAR_URL,
mcp_command: Some("buzz-dev-mcp"),
mcp_hooks: false,
underlying_cli: Some("grok"),
cli_install_commands: &["curl -fsSL https://x.ai/cli/install.sh | bash"],
cli_install_commands_windows: &[
"powershell.exe -NoProfile -ExecutionPolicy Bypass -Command \"irm https://x.ai/cli/install.ps1 | iex\"",
],
adapter_install_commands: &[],
install_instructions_url: "https://docs.x.ai/",
cli_install_hint: "Install Grok Build via the official xAI install script.",
adapter_install_hint: "",
skill_dir: Some(".grok/skills"),
supports_acp_model_switching: true,
model_env_var: None,
provider_env_var: None,
provider_locked: true,
default_env: &[],
config_file_path: Some("~/.grok/config.toml"),
config_file_format: Some("toml"),
supports_acp_native_config: false,
thinking_env_var: None,
max_tokens_env_var: None,
context_limit_env_var: None,
required_normalized_fields: &[],
login_hint: Some("Run `grok login` or set XAI_API_KEY."),
// No stable non-interactive auth-status subcommand yet; availability is
// based on finding the binary. Auth failures surface when a turn starts.
auth_probe_args: None,
},
KnownAcpRuntime {
id: "buzz-agent",
label: "Buzz Agent",
Expand Down Expand Up @@ -343,6 +382,14 @@ pub use overrides::{apply_agent_command_update, create_time_agent_command_overri
fn default_agent_args(command: &str) -> Option<Vec<String>> {
match normalize_command_identity(command).as_str() {
"goose" => Some(vec!["acp".to_string()]),
// Grok Build speaks ACP natively over stdio.
// `grok agent --always-approve stdio` keeps managed turns unattended.
"grok" | "grok-build" | "grok-acp" => Some(vec![
"agent".to_string(),
"--always-approve".to_string(),
"stdio".to_string(),
]),

"codex" | "codex-acp" | "claude-agent-acp" | "claude-code-acp" | "claude-code"
| "claudecode" | "buzz-agent" => Some(Vec::new()),
_ => None,
Expand All @@ -364,8 +411,10 @@ pub fn normalize_agent_args(command: &str, agent_args: Vec<String>) -> Vec<Strin
return default_args;
}

if normalized.len() == 1 && normalized[0].eq_ignore_ascii_case("acp") && default_args.is_empty()
{
// Legacy Goose-oriented callers often pass a bare `acp` arg. Treat that as
// "use the runtime default" for zero-arg adapters *and* for multi-arg
// native ACP entrypoints (e.g. Grok's `agent stdio`).
if normalized.len() == 1 && normalized[0].eq_ignore_ascii_case("acp") {
return default_args;
}

Expand Down
26 changes: 26 additions & 0 deletions desktop/src-tauri/src/managed_agents/discovery/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,32 @@ fn normalizes_buzz_agent_args_to_empty() {
);
}

#[test]
fn normalizes_grok_args_to_agent_stdio() {
let expected = vec![
"agent".to_string(),
"--always-approve".to_string(),
"stdio".to_string(),
];
assert_eq!(normalize_agent_args("grok", Vec::new()), expected);
assert_eq!(
normalize_agent_args("grok", vec!["acp".into()]),
expected
);
assert_eq!(
normalize_agent_args("/Users/me/.grok/bin/grok", Vec::new()),
expected
);
assert_eq!(
managed_agent_avatar_url("grok"),
Some(super::GROK_AVATAR_URL.to_string())
);
let runtime = super::known_acp_runtime_exact("grok").expect("grok runtime");
assert_eq!(runtime.commands, &["grok"]);
assert_eq!(runtime.underlying_cli, Some("grok"));
assert!(runtime.adapter_install_commands.is_empty());
}

#[test]
fn login_shell_lookup_treats_command_as_data() {
let marker =
Expand Down
1 change: 1 addition & 0 deletions desktop/src-tauri/src/managed_agents/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub(crate) const KNOWN_AGENT_BINARIES: &[&str] = &[
"codex-acp",
"codex_acp",
"goose",
"grok",
// buzz-dev-mcp's multicall personalities (rg, tree, buzz,
// git-credential-nostr, git-sign-nostr) are short-lived per-tool-call
// invocations — not listed here.
Expand Down
4 changes: 3 additions & 1 deletion desktop/src/features/onboarding/ui/agentReadiness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ export function resolveAgentReadiness(
}

if (
(preferredRuntime.id === "claude" || preferredRuntime.id === "codex") &&
(preferredRuntime.id === "claude" ||
preferredRuntime.id === "codex" ||
preferredRuntime.id === "grok") &&
(preferredRuntime.authStatus.status === "logged_in" ||
preferredRuntime.authStatus.status === "not_applicable")
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { AcpRuntimeCatalogEntry } from "@/shared/api/types";

export const ONBOARDING_RUNTIME_ORDER = ["claude", "codex"];
export const ONBOARDING_RUNTIME_ORDER = ["claude", "codex", "grok"];

const VISIBLE_ONBOARDING_RUNTIME_IDS = new Set<string>(
ONBOARDING_RUNTIME_ORDER,
Expand Down