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
10 changes: 6 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,19 @@ Buzz spans five repos. This one (`block/buzz`) is the OSS source for the relay,
| Repo | Purpose |
|------|---------|
| [block/buzz](https://github.com/block/buzz) | OSS source — relay, desktop app, mobile app, CLI, agent harness |
| [squareup/sprout-releases](https://github.com/squareup/sprout-releases) | Buildkite pipeline producing Block-signed macOS + iOS builds with `-block` version suffix |
| [squareup/buzz-releases](https://github.com/squareup/buzz-releases) | Buildkite pipeline producing Block-signed macOS + iOS builds with `-block` version suffix |
| [squareup/sprout-oss](https://github.com/squareup/sprout-oss) | CI pipeline building the relay Docker image and pushing to internal ECR |
| [squareup/block-coder-tf-stacks](https://github.com/squareup/block-coder-tf-stacks) | Terraform + ArgoCD deploying the relay to the staging Kubernetes cluster |
| [squareup/sprout-backend-blox](https://github.com/squareup/sprout-backend-blox) | Desktop backend provider script connecting Blox workstation agents to the relay |

Private pipeline repos that still use the pre-rename `sprout-*` GitHub names do so intentionally; the signed-desktop GitHub repo is `buzz-releases` (some Buildkite slugs may still say `sprout-releases`).

```
block/buzz (source)
├─► sprout-releases (desktop + mobile builds → Artifactory, GitHub, Mobile Releases)
├─► sprout-oss (relay Docker image → ECR)
├─► buzz-releases (desktop + mobile builds → Artifactory, GitHub, Mobile Releases)
├─► sprout-oss (relay Docker image → ECR; legacy name retained)
│ └─► block-coder-tf-stacks (Helm chart → ArgoCD → staging cluster)
└─── sprout-backend-blox (Blox compute provider for Desktop agent launch)
└─── sprout-backend-blox (Blox compute provider; legacy name retained)
```

See [RELEASING.md](RELEASING.md) for the desktop release flow and
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ dependency diagram.
automatically. No special access is required.

**Block team members:** See the internal
[sprout-releases CONTRIBUTING.md](https://github.com/squareup/sprout-releases/blob/main/CONTRIBUTING.md)
[buzz-releases CONTRIBUTING.md](https://github.com/squareup/buzz-releases/blob/main/CONTRIBUTING.md)
for team access setup, onboarding, and the full repo inventory. See
[RELEASING.md](RELEASING.md) for the release process.

Expand Down
91 changes: 91 additions & 0 deletions desktop/src-tauri/src/managed_agents/codex_home.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//! Buzz-owned Codex state directory under the nest (`~/.buzz/.codex`).
//!
//! Managed Codex agents must not write sessions into the user's personal
//! `~/.codex` store — that surfaces `.buzz` in Codex Desktop / ChatGPT Remote
//! (#2660). Auth is handed off via a symlink (or copy) of `auth.json` from the
//! user store so login still works.

use std::{fs, path::Path, path::PathBuf, process::Command};

use super::discovery::KnownAcpRuntime;
use super::nest::nest_dir;

/// Returns the Buzz-owned Codex home, creating it and handing off auth when needed.
pub fn isolated_codex_home() -> Option<PathBuf> {
let nest = nest_dir()?;
let codex_home = nest.join(".codex");
if let Err(error) = fs::create_dir_all(&codex_home) {
eprintln!(
"buzz-desktop: failed to create {}: {error}",
codex_home.display()
);
return None;
}
handoff_user_codex_auth(&codex_home);
Some(codex_home)
}

/// Set `CODEX_HOME` on a spawn command when the runtime is Codex.
pub fn apply_isolated_codex_home(command: &mut Command, runtime: Option<&KnownAcpRuntime>) {
if runtime.is_none_or(|runtime| runtime.id != "codex") {
return;
}
if let Some(codex_home) = isolated_codex_home() {
command.env("CODEX_HOME", codex_home);
}
}

fn handoff_user_codex_auth(codex_home: &Path) {
let Some(user_home) = dirs::home_dir() else {
return;
};
let user_codex = user_home.join(".codex");
// Already isolated / same path — nothing to link.
if user_codex == codex_home {
return;
}
let src = user_codex.join("auth.json");
let dst = codex_home.join("auth.json");
if !src.is_file() || dst.exists() {
return;
}
if let Err(error) = link_or_copy(&src, &dst) {
eprintln!(
"buzz-desktop: failed to hand off Codex auth {} → {}: {error}",
src.display(),
dst.display()
);
}
}

fn link_or_copy(src: &Path, dst: &Path) -> std::io::Result<()> {
#[cfg(unix)]
{
match std::os::unix::fs::symlink(src, dst) {
Ok(()) => return Ok(()),
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => return Ok(()),
Err(_) => {
// Fall through to copy (e.g. filesystem without symlink support).
}
}
}
fs::copy(src, dst).map(|_| ())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn link_or_copy_writes_auth_handoff() {
let tmp = tempfile::tempdir().unwrap();
let src = tmp.path().join("auth.json");
let dst_dir = tmp.path().join(".buzz").join(".codex");
let dst = dst_dir.join("auth.json");
fs::write(&src, b"{\"tokens\":[]}").unwrap();
fs::create_dir_all(&dst_dir).unwrap();
link_or_copy(&src, &dst).unwrap();
assert!(dst.is_file() || dst.is_symlink());
assert_eq!(fs::read_to_string(&dst).unwrap(), "{\"tokens\":[]}");
}
}
1 change: 1 addition & 0 deletions desktop/src-tauri/src/managed_agents/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub(crate) use agent_env::{
baked_build_env, build_buzz_agent_provider_defaults, discovery_env_with_baked_floor,
};
mod backend;
pub(crate) mod codex_home;
pub(crate) mod config_bridge;
mod discovery;
mod env_vars;
Expand Down
3 changes: 2 additions & 1 deletion desktop/src-tauri/src/managed_agents/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1716,6 +1716,8 @@ pub fn spawn_agent_child(
if let Some(ref path) = augmented_path {
command.env("PATH", path);
}
let runtime_meta = known_acp_runtime(&effective_command);
super::codex_home::apply_isolated_codex_home(&mut command, runtime_meta);
command.env("RUST_LOG", child_rust_log_filter());
command.env("BUZZ_PRIVATE_KEY", &record.private_key_nsec);
command.env("BUZZ_RELAY_URL", &effective_relay_url);
Expand All @@ -1732,7 +1734,6 @@ pub fn spawn_agent_child(
}
// Enable MCP hook tools (_Stop, _PostCompact) for agents that need them.
// Uses "*" because build_mcp_servers() hard-codes the server name to "buzz-mcp".
let runtime_meta = known_acp_runtime(&effective_command);
if runtime_meta.is_some_and(|r| r.mcp_hooks) {
command.env("MCP_HOOK_SERVERS", "*");
}
Expand Down
11 changes: 10 additions & 1 deletion desktop/src/features/sidebar/ui/CommunityRail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,20 @@ function CommunityButton({
{...dragAttributes}
{...dragListeners}
>
{/* Discord-style active pill — stays visible when a custom icon
covers the tile background (#2570). */}
{isActive ? (
<span
aria-hidden
className="absolute -left-1.5 top-1/2 h-5 w-1 -translate-y-1/2 rounded-r-full bg-sidebar-foreground"
data-testid={`community-rail-active-${community.id}`}
/>
) : null}
<span
className={cn(
"flex h-9 w-9 items-center justify-center overflow-hidden rounded-2xl text-xs font-semibold transition-all",
isActive
? "rounded-xl bg-primary text-primary-foreground"
? "rounded-xl bg-primary text-primary-foreground ring-2 ring-sidebar-foreground/40"
: "bg-sidebar-accent/60 text-sidebar-foreground/80 hover:rounded-xl hover:bg-primary/80 hover:text-primary-foreground",
pending && "opacity-60",
)}
Expand Down