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:
Alex Netsch
2025-12-19 17:58:01 +00:00
committed by GitHub
parent 165e832830
commit 2f5cef2dff
36 changed files with 478 additions and 152 deletions

View File

@@ -181,17 +181,25 @@ pub async fn follow_up(
.container()
.cleanup_actions_for_repos(&project_repos);
let working_dir = workspace
.agent_working_dir
.as_ref()
.filter(|dir| !dir.is_empty())
.cloned();
let action_type = if let Some(agent_session_id) = latest_agent_session_id {
ExecutorActionType::CodingAgentFollowUpRequest(CodingAgentFollowUpRequest {
prompt: prompt.clone(),
session_id: agent_session_id,
executor_profile_id: executor_profile_id.clone(),
working_dir: working_dir.clone(),
})
} else {
ExecutorActionType::CodingAgentInitialRequest(
executors::actions::coding_agent_initial::CodingAgentInitialRequest {
prompt,
executor_profile_id: executor_profile_id.clone(),
working_dir,
},
)
};

View File

@@ -142,6 +142,17 @@ pub async fn create_task_attempt(
.await?
.ok_or(SqlxError::RowNotFound)?;
let project = task
.parent_project(pool)
.await?
.ok_or(SqlxError::RowNotFound)?;
let agent_working_dir = project
.default_agent_working_dir
.as_ref()
.filter(|dir| !dir.is_empty())
.cloned();
let attempt_id = Uuid::new_v4();
let git_branch_name = deployment
.container()
@@ -152,6 +163,7 @@ pub async fn create_task_attempt(
pool,
&CreateWorkspace {
branch: git_branch_name.clone(),
agent_working_dir,
},
attempt_id,
payload.task_id,

View File

@@ -146,17 +146,25 @@ async fn trigger_pr_description_follow_up(
)
.await?;
let working_dir = workspace
.agent_working_dir
.as_ref()
.filter(|dir| !dir.is_empty())
.cloned();
// Build the action type (follow-up if session exists, otherwise initial)
let action_type = if let Some(agent_session_id) = latest_agent_session_id {
ExecutorActionType::CodingAgentFollowUpRequest(CodingAgentFollowUpRequest {
prompt,
session_id: agent_session_id,
executor_profile_id: executor_profile_id.clone(),
working_dir: working_dir.clone(),
})
} else {
ExecutorActionType::CodingAgentInitialRequest(CodingAgentInitialRequest {
prompt,
executor_profile_id: executor_profile_id.clone(),
working_dir,
})
};

View File

@@ -14,6 +14,7 @@ use axum::{
};
use db::models::{
image::TaskImage,
project::{Project, ProjectError},
repo::Repo,
task::{CreateTask, Task, TaskWithAttemptStatus, UpdateTask},
workspace::{CreateWorkspace, Workspace},
@@ -177,16 +178,27 @@ pub async fn create_task_and_start(
)
.await;
let project = Project::find_by_id(pool, task.project_id)
.await?
.ok_or(ProjectError::ProjectNotFound)?;
let attempt_id = Uuid::new_v4();
let git_branch_name = deployment
.container()
.git_branch_from_workspace(&attempt_id, &task.title)
.await;
let agent_working_dir = project
.default_agent_working_dir
.as_ref()
.filter(|dir: &&String| !dir.is_empty())
.cloned();
let workspace = Workspace::create(
pool,
&CreateWorkspace {
branch: git_branch_name,
agent_working_dir,
},
attempt_id,
task.id,