Smooth cursor onboarding (#1143)
* WIP cursor onboarding
Claude rebase fixes
fmt
WIP
WIP
WIP Add login button
Remove auto login check
Login needed detection
Setup script for login and install
Fix install+login case
Update task to inreview upon failure, cleanup
Feedback in next action box
Use errortype isntead of next action to communicate setup needed
* Add auto retry after install
* Fix setup needed detection
* Fix next_run_reason
* Lint and format
* i18n
* Rename to setup helper
* rename ErrorType to NormalizedEntryError
* Better setupHelpText i18n (vibe-kanban a8dd795e)
frontend/src/i18n/locales/en/tasks.json setupHelpText currently isn't very informative. We should change it to
"{agent_name} isn't setup correctly. Click 'Run Setup' to install it and login." or similar.
* Cursor login should be exempt from the 2 execution process limit (vibe-kanban deca56cd)
frontend/src/components/NormalizedConversation/NextActionCard.tsx
Display run setup even if there are more than 2 execution processes
* Move agent setup to server
This commit is contained in:
@@ -91,6 +91,8 @@ fn generate_types_content() -> String {
|
||||
executors::actions::coding_agent_initial::CodingAgentInitialRequest::decl(),
|
||||
executors::actions::coding_agent_follow_up::CodingAgentFollowUpRequest::decl(),
|
||||
server::routes::task_attempts::CreateTaskAttemptBody::decl(),
|
||||
server::routes::task_attempts::RunAgentSetupRequest::decl(),
|
||||
server::routes::task_attempts::RunAgentSetupResponse::decl(),
|
||||
server::routes::task_attempts::RebaseTaskAttemptRequest::decl(),
|
||||
server::routes::task_attempts::GitOperationError::decl(),
|
||||
server::routes::task_attempts::ReplaceProcessRequest::decl(),
|
||||
@@ -115,6 +117,7 @@ fn generate_types_content() -> String {
|
||||
executors::logs::FileChange::decl(),
|
||||
executors::logs::ActionType::decl(),
|
||||
executors::logs::TodoItem::decl(),
|
||||
executors::logs::NormalizedEntryError::decl(),
|
||||
executors::logs::ToolResult::decl(),
|
||||
executors::logs::ToolResultValueType::decl(),
|
||||
executors::logs::ToolStatus::decl(),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod cursor_setup;
|
||||
pub mod drafts;
|
||||
pub mod util;
|
||||
|
||||
@@ -27,7 +28,8 @@ use executors::{
|
||||
coding_agent_follow_up::CodingAgentFollowUpRequest,
|
||||
script::{ScriptContext, ScriptRequest, ScriptRequestLanguage},
|
||||
},
|
||||
profile::ExecutorProfileId,
|
||||
executors::{CodingAgent, ExecutorError},
|
||||
profile::{ExecutorConfigs, ExecutorProfileId},
|
||||
};
|
||||
use git2::BranchType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -141,6 +143,14 @@ impl CreateTaskAttemptBody {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, TS)]
|
||||
pub struct RunAgentSetupRequest {
|
||||
pub executor_profile_id: ExecutorProfileId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, TS)]
|
||||
pub struct RunAgentSetupResponse {}
|
||||
|
||||
#[axum::debug_handler]
|
||||
pub async fn create_task_attempt(
|
||||
State(deployment): State<DeploymentImpl>,
|
||||
@@ -194,6 +204,35 @@ pub async fn create_task_attempt(
|
||||
Ok(ResponseJson(ApiResponse::success(task_attempt)))
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
pub async fn run_agent_setup(
|
||||
Extension(task_attempt): Extension<TaskAttempt>,
|
||||
State(deployment): State<DeploymentImpl>,
|
||||
Json(payload): Json<RunAgentSetupRequest>,
|
||||
) -> Result<ResponseJson<ApiResponse<RunAgentSetupResponse>>, ApiError> {
|
||||
let executor_profile_id = payload.executor_profile_id;
|
||||
let config = ExecutorConfigs::get_cached();
|
||||
let coding_agent = config.get_coding_agent_or_default(&executor_profile_id);
|
||||
match coding_agent {
|
||||
CodingAgent::CursorAgent(_) => {
|
||||
cursor_setup::run_cursor_setup(&deployment, &task_attempt).await?;
|
||||
}
|
||||
_ => return Err(ApiError::Executor(ExecutorError::SetupHelperNotSupported)),
|
||||
}
|
||||
|
||||
deployment
|
||||
.track_if_analytics_allowed(
|
||||
"agent_setup_script_executed",
|
||||
serde_json::json!({
|
||||
"executor_profile_id": executor_profile_id.to_string(),
|
||||
"attempt_id": task_attempt.id.to_string(),
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(ResponseJson(ApiResponse::success(RunAgentSetupResponse {})))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, TS)]
|
||||
pub struct CreateFollowUpAttempt {
|
||||
pub prompt: String,
|
||||
@@ -1476,6 +1515,7 @@ pub fn router(deployment: &DeploymentImpl) -> Router<DeploymentImpl> {
|
||||
let task_attempt_id_router = Router::new()
|
||||
.route("/", get(get_task_attempt))
|
||||
.route("/follow-up", post(follow_up))
|
||||
.route("/run-agent-setup", post(run_agent_setup))
|
||||
.route(
|
||||
"/draft",
|
||||
get(drafts::get_draft)
|
||||
|
||||
99
crates/server/src/routes/task_attempts/cursor_setup.rs
Normal file
99
crates/server/src/routes/task_attempts/cursor_setup.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
use db::models::{
|
||||
execution_process::{ExecutionProcess, ExecutionProcessRunReason},
|
||||
task_attempt::{TaskAttempt, TaskAttemptError},
|
||||
};
|
||||
use deployment::Deployment;
|
||||
use executors::{
|
||||
actions::{
|
||||
ExecutorAction, ExecutorActionType,
|
||||
script::{ScriptContext, ScriptRequest, ScriptRequestLanguage},
|
||||
},
|
||||
executors::cursor::CursorAgent,
|
||||
};
|
||||
use services::services::container::ContainerService;
|
||||
|
||||
use crate::{error::ApiError, routes::task_attempts::ensure_worktree_path};
|
||||
|
||||
pub async fn run_cursor_setup(
|
||||
deployment: &crate::DeploymentImpl,
|
||||
task_attempt: &TaskAttempt,
|
||||
) -> Result<ExecutionProcess, ApiError> {
|
||||
let latest_process = ExecutionProcess::find_latest_by_task_attempt_and_run_reason(
|
||||
&deployment.db().pool,
|
||||
task_attempt.id,
|
||||
&ExecutionProcessRunReason::CodingAgent,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let executor_action = if let Some(latest_process) = latest_process {
|
||||
let latest_action = latest_process
|
||||
.executor_action()
|
||||
.map_err(|e| ApiError::TaskAttempt(TaskAttemptError::ValidationError(e.to_string())))?;
|
||||
get_setup_helper_action()
|
||||
.await?
|
||||
.append_action(latest_action.to_owned())
|
||||
} else {
|
||||
get_setup_helper_action().await?
|
||||
};
|
||||
|
||||
let _ = ensure_worktree_path(deployment, task_attempt).await?;
|
||||
|
||||
let execution_process = deployment
|
||||
.container()
|
||||
.start_execution(
|
||||
task_attempt,
|
||||
&executor_action,
|
||||
&ExecutionProcessRunReason::SetupScript,
|
||||
)
|
||||
.await?;
|
||||
Ok(execution_process)
|
||||
}
|
||||
|
||||
async fn get_setup_helper_action() -> Result<ExecutorAction, ApiError> {
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
let base_command = CursorAgent::base_command();
|
||||
// First action: Install
|
||||
let install_script = format!(
|
||||
r#"#!/bin/bash
|
||||
set -e
|
||||
if ! command -v {base_command} &> /dev/null; then
|
||||
echo "Installing Cursor CLI..."
|
||||
curl https://cursor.com/install -fsS | bash
|
||||
echo "Installation complete!"
|
||||
else
|
||||
echo "Cursor CLI already installed"
|
||||
fi
|
||||
"#
|
||||
);
|
||||
|
||||
let install_request = ScriptRequest {
|
||||
script: install_script,
|
||||
language: ScriptRequestLanguage::Bash,
|
||||
context: ScriptContext::SetupScript,
|
||||
};
|
||||
|
||||
// Second action (chained): Login
|
||||
let login_script = format!("{base_command} login");
|
||||
let login_request = ScriptRequest {
|
||||
script: login_script,
|
||||
language: ScriptRequestLanguage::Bash,
|
||||
context: ScriptContext::SetupScript,
|
||||
};
|
||||
|
||||
// Chain them: install → login
|
||||
Ok(ExecutorAction::new(
|
||||
ExecutorActionType::ScriptRequest(install_request),
|
||||
Some(Box::new(ExecutorAction::new(
|
||||
ExecutorActionType::ScriptRequest(login_request),
|
||||
None,
|
||||
))),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use executors::executors::ExecutorError::SetupHelperNotSupported;
|
||||
Err(ApiError::Executor(ExecutorError::SetupHelperNotSupported))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user