diff --git a/AGENTS.md b/AGENTS.md index 3edfb34f60..f8a4867e5a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d6278f5eba..c9e9735fdd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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. diff --git a/desktop/src-tauri/src/managed_agents/codex_home.rs b/desktop/src-tauri/src/managed_agents/codex_home.rs new file mode 100644 index 0000000000..47deac2e2a --- /dev/null +++ b/desktop/src-tauri/src/managed_agents/codex_home.rs @@ -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 { + 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\":[]}"); + } +} diff --git a/desktop/src-tauri/src/managed_agents/mod.rs b/desktop/src-tauri/src/managed_agents/mod.rs index 66f3d882b9..c33f7146c6 100644 --- a/desktop/src-tauri/src/managed_agents/mod.rs +++ b/desktop/src-tauri/src/managed_agents/mod.rs @@ -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; diff --git a/desktop/src-tauri/src/managed_agents/runtime.rs b/desktop/src-tauri/src/managed_agents/runtime.rs index 6687cbbcf2..98a1fb38cd 100644 --- a/desktop/src-tauri/src/managed_agents/runtime.rs +++ b/desktop/src-tauri/src/managed_agents/runtime.rs @@ -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); @@ -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", "*"); } diff --git a/desktop/src/features/sidebar/ui/CommunityRail.tsx b/desktop/src/features/sidebar/ui/CommunityRail.tsx index 2737a41f4f..31e832cdc1 100644 --- a/desktop/src/features/sidebar/ui/CommunityRail.tsx +++ b/desktop/src/features/sidebar/ui/CommunityRail.tsx @@ -135,11 +135,20 @@ function CommunityButton({ {...dragAttributes} {...dragListeners} > + {/* Discord-style active pill — stays visible when a custom icon + covers the tile background (#2570). */} + {isActive ? ( + + ) : null}