Find installed executors (vibe-kanban) (#610)

* feat: add availability check for coding agents and set smart defaults for new users

* Add executor discovery (vibe-kanban d65d61aa)

Plan: Add executor discovery with appropriate checks
Understanding
You're absolutely right! Checking if npx is available is useless for Opencode/Qwen since it doesn't tell us if those specific agents have been set up. We should follow the pattern used by other executors:

Binary-based executors (like Cursor): Check if the binary exists
NPX-based executors (like Claude, Gemini, Opencode, Qwen): Check for their settings files
Implementation Plan
1. Cursor executor (cursor.rs, after line 407)
Since Cursor uses a standalone binary:

async fn check_availability(&self) -> bool {
    utils::shell::resolve_executable_path("cursor-agent").is_some()
}
Import needed: Add shell::resolve_executable_path to the utils import
2. Opencode executor (opencode.rs, after line 188)
Follow the pattern of other NPX-based executors - check for settings file:

async fn check_availability(&self) -> bool {
    self.default_mcp_config_path()
        .map(|path| path.exists())
        .unwrap_or(false)
}
This will check for the opencode.json file in the appropriate location (XDG on Unix, config_dir on Windows).

3. QwenCode executor (qwen.rs, after line 140)
Same approach as other NPX-based executors:

async fn check_availability(&self) -> bool {
    self.default_mcp_config_path()
        .map(|path| path.exists())
        .unwrap_or(false)
}
This will check for ~/.qwen/settings.json.

Why this approach is correct:
Cursor: Has a specific binary that must be installed → check for binary
Opencode/Qwen: Are NPX-based like Claude/Gemini → check for settings files
Consistency: Follows the existing pattern where NPX-based executors check for settings files
Meaningful detection: Settings files indicate the agent has been configured/used before, which is what we want for "availability"
This gives us meaningful availability detection that matches the existing patterns in the codebase.

* Remove duplicated config paths (vibe-kanban 207e0410)

In  some executors, like crates/executors/src/executors/amp.rs , Paths for checking availability and mcp server are identical but not reused. We should not duplicate code. Consider changing the default availability check to just check for the mcp config

* feat: implement recommended executor profile retrieval for new users

* fmt
This commit is contained in:
Alex Netsch
2025-09-03 18:14:50 +01:00
committed by GitHub
parent c100e7ccaf
commit 4535149405
5 changed files with 46 additions and 8 deletions

View File

@@ -14,7 +14,7 @@ use utils::{
},
msg_store::MsgStore,
path::make_path_relative,
shell::get_shell_command,
shell::{get_shell_command, resolve_executable_path},
};
use crate::{
@@ -405,6 +405,10 @@ impl StandardCodingAgentExecutor for Cursor {
fn default_mcp_config_path(&self) -> Option<std::path::PathBuf> {
dirs::home_dir().map(|home| home.join(".cursor").join("mcp.json"))
}
async fn check_availability(&self) -> bool {
resolve_executable_path("cursor-agent").is_some()
}
}
fn strip_cursor_ascii_art_banner(line: String) -> String {

View File

@@ -145,4 +145,10 @@ pub trait StandardCodingAgentExecutor {
// MCP configuration methods
fn default_mcp_config_path(&self) -> Option<std::path::PathBuf>;
async fn check_availability(&self) -> bool {
self.default_mcp_config_path()
.map(|path| path.exists())
.unwrap_or(false)
}
}

View File

@@ -6,7 +6,7 @@ use serde::{Deserialize, Deserializer, Serialize, de::Error as DeError};
use thiserror::Error;
use ts_rs::TS;
use crate::executors::{BaseCodingAgent, CodingAgent};
use crate::executors::{BaseCodingAgent, CodingAgent, StandardCodingAgentExecutor};
/// Return the canonical form for variant keys.
/// "DEFAULT" is kept as-is
@@ -40,6 +40,9 @@ pub enum ProfileError {
#[error(transparent)]
Serde(#[from] serde_json::Error),
#[error("No available executor profile")]
NoAvailableExecutorProfile,
}
lazy_static! {
@@ -406,4 +409,19 @@ impl ExecutorConfigs {
.expect("No default variant found")
})
}
/// Get the first available executor profile for new users
pub async fn get_recommended_executor_profile(
&self,
) -> Result<ExecutorProfileId, ProfileError> {
for &base_agent in self.executors.keys() {
let profile_id = ExecutorProfileId::new(base_agent);
if let Some(coding_agent) = self.get_coding_agent(&profile_id)
&& coding_agent.check_availability().await
{
tracing::info!("Detected available executor: {}", base_agent);
return Ok(profile_id);
}
}
Err(ProfileError::NoAvailableExecutorProfile)
}
}

View File

@@ -3,6 +3,7 @@ use std::{collections::HashMap, sync::Arc};
use async_trait::async_trait;
use db::DBService;
use deployment::{Deployment, DeploymentError};
use executors::profile::ExecutorConfigs;
use services::services::{
analytics::{AnalyticsConfig, AnalyticsContext, AnalyticsService, generate_user_id},
auth::AuthService,
@@ -44,6 +45,13 @@ impl Deployment for LocalDeployment {
async fn new() -> Result<Self, DeploymentError> {
let mut raw_config = load_config_from_file(&config_path()).await;
let profiles = ExecutorConfigs::get_cached();
if !raw_config.onboarding_acknowledged
&& let Ok(recommended_executor) = profiles.get_recommended_executor_profile().await
{
raw_config.executor_profile = recommended_executor;
}
// Check if app version has changed and set release notes flag
{
let current_version = utils::version::APP_VERSION;

View File

@@ -39,15 +39,17 @@ interface OnboardingDialogProps {
}
export function OnboardingDialog({ open, onComplete }: OnboardingDialogProps) {
const [profile, setProfile] = useState<ExecutorProfileId>({
executor: BaseCodingAgent.CLAUDE_CODE,
variant: null,
});
const { profiles, config } = useUserSystem();
const [profile, setProfile] = useState<ExecutorProfileId>(
config?.executor_profile || {
executor: BaseCodingAgent.CLAUDE_CODE,
variant: null,
}
);
const [editorType, setEditorType] = useState<EditorType>(EditorType.VS_CODE);
const [customCommand, setCustomCommand] = useState<string>('');
const { profiles } = useUserSystem();
const handleComplete = () => {
onComplete({
profile,