Add agent working directory setting to run coding agents from repo directory (Vibe Kanban) (#1617)
* Done! The migration has been updated to copy `dev_script_working_dir` values to `agent_working_dir` for existing projects. The change: **`vibe-kanban/crates/db/migrations/20251219000000_add_agent_working_dir_to_projects.sql`**: ```sql -- Add column with empty default first ALTER TABLE projects ADD COLUMN agent_working_dir TEXT DEFAULT ''; -- Copy existing dev_script_working_dir values to agent_working_dir UPDATE projects SET agent_working_dir = dev_script_working_dir WHERE dev_script_working_dir IS NOT NULL AND dev_script_working_dir != ''; ``` This ensures that when the migration runs, existing projects will have their `agent_working_dir` automatically populated from their existing `dev_script_working_dir` setting. * Done! The migration has been updated to only copy `dev_script_working_dir` to `agent_working_dir` for single-repo projects. Multi-repo projects will keep `agent_working_dir` empty (None). The key change in the migration: ```sql UPDATE projects SET agent_working_dir = dev_script_working_dir WHERE dev_script_working_dir IS NOT NULL AND dev_script_working_dir != '' AND (SELECT COUNT(*) FROM project_repos WHERE project_repos.project_id = projects.id) = 1; ``` The subquery `(SELECT COUNT(*) FROM project_repos WHERE project_repos.project_id = projects.id) = 1` ensures only projects with exactly one repository get the default value copied. * Done! All tasks completed: 1. **Resolved the rebase conflict** in `task_attempts.rs` - removed the duplicate `follow_up` function since it was moved to `sessions/mod.rs` on main 2. **Updated `sessions/mod.rs`** with the `working_dir` changes - added the code to get `agent_working_dir` from project settings and pass it to both `CodingAgentFollowUpRequest` and `CodingAgentInitialRequest` 3. **Ran `npm run prepare-db`** - successfully prepared the database and updated sqlx cache 4. **Verified compilation** - all crates compile successfully The migration now only copies `dev_script_working_dir` to `agent_working_dir` for single-repo projects (projects with exactly 1 repo in `project_repos` table). Multi-repo projects will keep `agent_working_dir` empty. * Done. Removed all 6 comments as requested. * Done! The implementation is now complete: 1. **Database migration** - Copies `dev_script_working_dir` to `agent_working_dir` for single-repo projects at migration time 2. **Runtime backfill** - `backfill_repo_names()` in `container.rs` sets `agent_working_dir` for existing single-repo projects where it's still empty 3. **Project creation** - New single-repo projects automatically get `agent_working_dir` set to the repo name All three scenarios are now covered for setting `agent_working_dir` on single-repo projects. * Done! The frontend UI for `agent_working_dir` has been added: 1. Added `agent_working_dir: string` to `ProjectFormState` interface 2. Added `agent_working_dir` to `projectToFormState` function 3. Added `agent_working_dir` to the `handleSave` payload 4. Added the input field after the dev server working directory input * All tasks are complete. The implementation is finished: **Summary of changes:** 1. **Project model**: Renamed `agent_working_dir` to `default_agent_working_dir` - this is now a default value for new workspaces 2. **Workspace model**: Added `agent_working_dir` field - this captures the project's default at workspace creation time 3. **Workspace creation**: Updated `create_task_attempt` to fetch the project's `default_agent_working_dir` and pass it to the workspace 4. **Execution sites**: Updated all 4 locations to use `workspace.agent_working_dir` instead of `project.default_agent_working_dir`: - `container.rs` (start_workspace) - `container.rs` (start_queued_follow_up) - `sessions/mod.rs` (follow_up) - `task_attempts/pr.rs` (trigger_pr_description_follow_up) 5. **Frontend**: Updated `ProjectSettings.tsx` to use `default_agent_working_dir` 6. **Migrations**: Two migrations added - one to rename the project column and one to add the workspace column * Cleanup script changes for workspace a87cad74-ed7c-402b-b3f3-413a939a13a9 * Done. The migrations have been squashed into a single migration file `20251219000000_add_agent_working_dir_to_projects.sql` which now handles both the project and workspace table changes. The separate workspace migration file has been deleted and `npm run prepare-db` completes successfully. * Done. The i18n check passes with all checks passing: - No new literal strings introduced - No duplicate keys found in JSON files - Translation keys are consistent across locales (en, es, ja, ko, zh-Hans) * All checks pass: - `npm run check` ✅ (frontend TypeScript and backend Rust) - `npm run prepare-db` ✅ (migrations and sqlx query cache) * `npm run lint` passes now. * Done. I've added the logic to `crates/services/src/services/project.rs` in the `add_repository()` function. When a second repository is added to a project (repo count becomes exactly 2), the `default_agent_working_dir` field is automatically cleared by setting it to an empty string. * Cleanup script changes for workspace a87cad74-ed7c-402b-b3f3-413a939a13a9 * Both checks pass. The fix now correctly: 1. Counts repos **before** adding the new one 2. Only clears `default_agent_working_dir` if the previous count was exactly 1 (meaning we're going from 1 to 2 repos) * Done. The fix now: 1. Added a dedicated `Project::clear_default_agent_working_dir()` method in `crates/db/src/models/project.rs` that only updates the `default_agent_working_dir` field without touching other fields 2. Updated `add_repository()` in `crates/services/src/services/project.rs` to use this new method when the repo count goes from 1 to 2
This commit is contained in:
@@ -351,7 +351,7 @@ pub trait ContainerService {
|
||||
}
|
||||
|
||||
/// Backfill repo names that were migrated with a sentinel placeholder.
|
||||
/// Also backfills dev_script_working_dir for single-repo projects.
|
||||
/// Also backfills dev_script_working_dir and agent_working_dir for single-repo projects.
|
||||
async fn backfill_repo_names(&self) -> Result<(), ContainerError> {
|
||||
let pool = &self.db().pool;
|
||||
let repos = Repo::list_needing_name_fix(pool).await?;
|
||||
@@ -372,33 +372,51 @@ pub trait ContainerService {
|
||||
|
||||
Repo::update_name(pool, repo.id, &name, &name).await?;
|
||||
|
||||
// Also update dev_script_working_dir for single-repo projects
|
||||
// Also update dev_script_working_dir and agent_working_dir for single-repo projects
|
||||
let project_repos = ProjectRepo::find_by_repo_id(pool, repo.id).await?;
|
||||
for pr in project_repos {
|
||||
let all_repos = ProjectRepo::find_by_project_id(pool, pr.project_id).await?;
|
||||
if all_repos.len() == 1
|
||||
&& let Some(project) = Project::find_by_id(pool, pr.project_id).await?
|
||||
&& project
|
||||
{
|
||||
let needs_dev_script_working_dir = project
|
||||
.dev_script
|
||||
.as_ref()
|
||||
.map(|s| !s.is_empty())
|
||||
.unwrap_or(false)
|
||||
&& project
|
||||
.dev_script_working_dir
|
||||
&& project
|
||||
.dev_script_working_dir
|
||||
.as_ref()
|
||||
.map(|s| s.is_empty())
|
||||
.unwrap_or(true);
|
||||
|
||||
let needs_default_agent_working_dir = project
|
||||
.default_agent_working_dir
|
||||
.as_ref()
|
||||
.map(|s| s.is_empty())
|
||||
.unwrap_or(true)
|
||||
{
|
||||
Project::update(
|
||||
pool,
|
||||
pr.project_id,
|
||||
&UpdateProject {
|
||||
name: Some(project.name.clone()),
|
||||
dev_script: project.dev_script.clone(),
|
||||
dev_script_working_dir: Some(name.clone()),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
.unwrap_or(true);
|
||||
|
||||
if needs_dev_script_working_dir || needs_default_agent_working_dir {
|
||||
Project::update(
|
||||
pool,
|
||||
pr.project_id,
|
||||
&UpdateProject {
|
||||
name: Some(project.name.clone()),
|
||||
dev_script: project.dev_script.clone(),
|
||||
dev_script_working_dir: if needs_dev_script_working_dir {
|
||||
Some(name.clone())
|
||||
} else {
|
||||
project.dev_script_working_dir.clone()
|
||||
},
|
||||
default_agent_working_dir: if needs_default_agent_working_dir {
|
||||
Some(name.clone())
|
||||
} else {
|
||||
project.default_agent_working_dir.clone()
|
||||
},
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -909,10 +927,17 @@ pub trait ContainerService {
|
||||
|
||||
let cleanup_action = self.cleanup_actions_for_repos(&project_repos);
|
||||
|
||||
let working_dir = workspace
|
||||
.agent_working_dir
|
||||
.as_ref()
|
||||
.filter(|dir| !dir.is_empty())
|
||||
.cloned();
|
||||
|
||||
let coding_action = ExecutorAction::new(
|
||||
ExecutorActionType::CodingAgentInitialRequest(CodingAgentInitialRequest {
|
||||
prompt,
|
||||
executor_profile_id: executor_profile_id.clone(),
|
||||
working_dir,
|
||||
}),
|
||||
cleanup_action.map(Box::new),
|
||||
);
|
||||
|
||||
@@ -110,11 +110,31 @@ impl ProjectService {
|
||||
.await
|
||||
.map_err(|e| ProjectServiceError::Project(ProjectError::CreateFailed(e.to_string())))?;
|
||||
|
||||
for repo in normalized_repos {
|
||||
let mut created_repo: Option<Repo> = None;
|
||||
for repo in &normalized_repos {
|
||||
let repo_entity =
|
||||
Repo::find_or_create(pool, Path::new(&repo.git_repo_path), &repo.display_name)
|
||||
.await?;
|
||||
ProjectRepo::create(pool, project.id, repo_entity.id).await?;
|
||||
if created_repo.is_none() {
|
||||
created_repo = Some(repo_entity);
|
||||
}
|
||||
}
|
||||
|
||||
if normalized_repos.len() == 1
|
||||
&& let Some(repo) = created_repo
|
||||
{
|
||||
Project::update(
|
||||
pool,
|
||||
project.id,
|
||||
&UpdateProject {
|
||||
name: None,
|
||||
dev_script: None,
|
||||
dev_script_working_dir: None,
|
||||
default_agent_working_dir: Some(repo.name),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(project)
|
||||
@@ -185,6 +205,11 @@ impl ProjectService {
|
||||
let path = repo_service.normalize_path(&payload.git_repo_path)?;
|
||||
repo_service.validate_git_repo_path(&path)?;
|
||||
|
||||
// Count repos before adding
|
||||
let repo_count_before = ProjectRepo::find_by_project_id(pool, project_id)
|
||||
.await?
|
||||
.len();
|
||||
|
||||
let repository = ProjectRepo::add_repo_to_project(
|
||||
pool,
|
||||
project_id,
|
||||
@@ -202,6 +227,11 @@ impl ProjectService {
|
||||
_ => ProjectServiceError::RepositoryNotFound,
|
||||
})?;
|
||||
|
||||
// If project just went from 1 to 2 repos, clear default_agent_working_dir
|
||||
if repo_count_before == 1 {
|
||||
Project::clear_default_agent_working_dir(pool, project_id).await?;
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
"Added repository {} to project {} (path: {})",
|
||||
repository.id,
|
||||
|
||||
Reference in New Issue
Block a user