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
29 changes: 29 additions & 0 deletions src-tauri/src/acp/binary_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,35 @@ pub fn detect_installed_version(
installed_version_for_agent(agent_type, cmd_name)
}

/// Resolve a user-managed binary that can launch the registry command.
///
/// Most binary agents expose the same command name on PATH. OpenCode Desktop
/// for macOS is the notable exception: it installs an app bundle whose ACP-capable
/// CLI is `Contents/MacOS/opencode-cli`, not a PATH-visible `opencode`.
pub fn resolve_system_binary_for_agent(agent_type: AgentType, cmd_name: &str) -> Option<PathBuf> {
if let Ok(path) = which::which(cmd_name) {
return Some(path);
}

if agent_type == AgentType::OpenCode && cmd_name == "opencode" {
let mut candidates = vec![PathBuf::from(
"/Applications/OpenCode.app/Contents/MacOS/opencode-cli",
)];
if let Some(home) = dirs::home_dir() {
candidates.push(
home.join("Applications")
.join("OpenCode.app")
.join("Contents")
.join("MacOS")
.join("opencode-cli"),
);
}
return candidates.into_iter().find(|path| path.is_file());
}

None
}

/// Return the best cached binary across all installed versions.
///
/// This returns the path + version label of the highest semver-ish
Expand Down
27 changes: 19 additions & 8 deletions src-tauri/src/acp/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,14 +279,25 @@ async fn build_agent(
// `src/contexts/acp-connections-context.tsx` to surface a
// localized install prompt. Do not change the wording.
let (binary_path, cached_version) =
crate::acp::binary_cache::find_best_cached_binary_for_agent(agent_type, cmd)?
.ok_or_else(|| {
AcpError::SdkNotInstalled(format!(
"{} is not installed. Please install it in Agent Settings.",
meta.name
))
})?;
if cached_version == registry_version {
match crate::acp::binary_cache::find_best_cached_binary_for_agent(agent_type, cmd)?
{
Some(cached) => cached,
None => {
let path = crate::acp::binary_cache::resolve_system_binary_for_agent(
agent_type, cmd,
)
.ok_or_else(|| {
AcpError::SdkNotInstalled(format!(
"{} is not installed. Please install it in Agent Settings.",
meta.name
))
})?;
(path, "system".to_string())
}
};
if cached_version == "system" {
eprintln!("[ACP][{}] Using system binary {}", meta.name, binary_path.display());
} else if cached_version == registry_version {
eprintln!("[ACP][{}] Using cached binary {cached_version}", meta.name);
} else {
eprintln!(
Expand Down
36 changes: 25 additions & 11 deletions src-tauri/src/acp/preflight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,15 +433,17 @@ async fn check_binary_environment(
};
checks.push(platform_check);

// Check binary cache.
// Check binary cache / system binary.
//
// Pass as long as *any* cached version is present — the session-page
// connect path uses the best cached version via
// `find_best_cached_binary_for_agent`, so an older-but-working cache
// should still be considered "ready". If the cached version differs
// from the registry's recommended version, we note it in the message
// but still pass — the Settings page's version-badge flow is the
// canonical place to surface "upgrade available".
// canonical place to surface "upgrade available". If Codeg did not
// download the binary but the command is already available on PATH (for
// example a user-managed `opencode`), that is launchable too.
if platform_supported {
let cache_check = match binary_cache::find_best_cached_binary_for_agent(agent_type, cmd) {
Ok(Some((_, cached_version))) => {
Expand All @@ -458,15 +460,27 @@ async fn check_binary_environment(
fixes: vec![],
}
}
Ok(None) => CheckItem {
check_id: "binary_cached".into(),
label: "Binary cache".into(),
status: CheckStatus::Warn,
message:
"Binary is not installed. Download it from Agent Settings before connecting."
.into(),
fixes: vec![],
},
Ok(None) => {
if let Some(path) = binary_cache::resolve_system_binary_for_agent(agent_type, cmd) {
CheckItem {
check_id: "binary_cached".into(),
label: "Binary".into(),
status: CheckStatus::Pass,
message: format!("Using system binary {}", path.display()),
fixes: vec![],
}
} else {
CheckItem {
check_id: "binary_cached".into(),
label: "Binary cache".into(),
status: CheckStatus::Warn,
message:
"Binary is not installed. Download it from Agent Settings before connecting."
.into(),
fixes: vec![],
}
}
}
Err(_) => CheckItem {
check_id: "binary_cached".into(),
label: "Binary cache".into(),
Expand Down
11 changes: 11 additions & 0 deletions src-tauri/src/acp/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,13 @@ pub struct AcpAgentInfo {
pub enabled: bool,
pub sort_order: i32,
pub installed_version: Option<String>,
/// Version of the upstream/original CLI detected on PATH (for example
/// `claude` or `codex`) when the managed ACP adapter/wrapper itself is not
/// installed. This is informational for Settings and must not be treated as
/// ACP-launch readiness.
pub base_cli_version: Option<String>,
pub base_cli_command: Option<String>,
pub base_cli_package: Option<String>,
pub env: BTreeMap<String, String>,
pub config_json: Option<String>,
pub config_file_path: Option<String>,
Expand All @@ -512,6 +519,10 @@ pub struct AcpAgentStatus {
pub available: bool,
pub enabled: bool,
pub installed_version: Option<String>,
/// Informational upstream/original CLI detection; see `AcpAgentInfo`.
pub base_cli_version: Option<String>,
pub base_cli_command: Option<String>,
pub base_cli_package: Option<String>,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
Expand Down
Loading