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

@@ -86,6 +86,7 @@ export function NoServerContent({
name: null,
dev_script: script,
dev_script_working_dir: project.dev_script_working_dir ?? null,
default_agent_working_dir: project.default_agent_working_dir ?? null,
},
},
{

View File

@@ -349,6 +349,11 @@
"placeholder": "e.g., my-repo",
"helper": "The directory to run the dev server script from, relative to the workspace root. Leave empty to run from the workspace root."
},
"agentWorkingDir": {
"label": "Agent Working Directory",
"placeholder": "e.g., my-repo",
"helper": "Default directory for new workspaces to run the coding agent from, relative to the workspace root. This value is captured when a workspace is created and won't affect existing workspaces. For single-repo projects, this defaults to the repo name. Leave empty to run from the workspace root."
},
"cleanup": {
"label": "Cleanup Script",
"helper": "This script runs from within the worktree after coding agent execution, only if changes were made. Use it for quality assurance tasks like running linters, formatters, tests, or other validation steps. If no changes are made, this script is skipped."

View File

@@ -349,6 +349,11 @@
"placeholder": "ej., mi-repo",
"helper": "El directorio desde el cual ejecutar el script del servidor de desarrollo, relativo a la raíz del workspace. Déjalo vacío para ejecutar desde la raíz del workspace."
},
"agentWorkingDir": {
"label": "Directorio de Trabajo del Agente",
"placeholder": "ej., mi-repo",
"helper": "Directorio predeterminado para nuevos workspaces donde ejecutar el agente de codificación, relativo a la raíz del workspace. Este valor se captura cuando se crea un workspace y no afectará a los workspaces existentes. Para proyectos de un solo repositorio, esto se establece por defecto al nombre del repositorio. Déjalo vacío para ejecutar desde la raíz del workspace."
},
"cleanup": {
"label": "Script de Limpieza",
"helper": "Este script se ejecuta desde dentro del worktree después de la ejecución del agente de codificación, solo si se realizaron cambios. Úsalo para tareas de garantía de calidad como ejecutar linters, formateadores, pruebas u otros pasos de validación. Si no se realizan cambios, se omite este script."

View File

@@ -349,6 +349,11 @@
"placeholder": "例my-repo",
"helper": "開発サーバースクリプトを実行するディレクトリ。ワークスペースルートからの相対パス。空欄にするとワークスペースルートから実行します。"
},
"agentWorkingDir": {
"label": "エージェント作業ディレクトリ",
"placeholder": "例my-repo",
"helper": "新しいワークスペースでコーディングエージェントを実行するデフォルトディレクトリ。ワークスペースルートからの相対パス。この値はワークスペース作成時に保存され、既存のワークスペースには影響しません。単一リポジトリプロジェクトの場合、リポジトリ名がデフォルトになります。空欄にするとワークスペースルートから実行します。"
},
"cleanup": {
"label": "クリーンアップスクリプト",
"helper": "このスクリプトはワークツリー内から、コーディングエージェントの実行後に実行されます(変更が行われた場合のみ)。リンター、フォーマッター、テスト、またはその他の検証ステップの実行など、品質保証タスクに使用してください。変更がない場合、このスクリプトはスキップされます。"

View File

@@ -349,6 +349,11 @@
"placeholder": "예: my-repo",
"helper": "개발 서버 스크립트를 실행할 디렉토리로, 워크스페이스 루트 기준 상대 경로입니다. 비워두면 워크스페이스 루트에서 실행됩니다."
},
"agentWorkingDir": {
"label": "에이전트 작업 디렉토리",
"placeholder": "예: my-repo",
"helper": "새 워크스페이스에서 코딩 에이전트를 실행할 기본 디렉토리로, 워크스페이스 루트 기준 상대 경로입니다. 이 값은 워크스페이스 생성 시 저장되며 기존 워크스페이스에는 영향을 주지 않습니다. 단일 저장소 프로젝트의 경우 저장소 이름이 기본값입니다. 비워두면 워크스페이스 루트에서 실행됩니다."
},
"cleanup": {
"label": "정리 스크립트",
"helper": "이 스크립트는 워크트리 내부에서 코딩 에이전트 실행 후에 실행됩니다(변경 사항이 있는 경우에만). 린터, 포맷터, 테스트 또는 기타 검증 단계 실행과 같은 품질 보증 작업에 사용하세요. 변경 사항이 없으면 이 스크립트를 건너뜁니다."

View File

@@ -349,6 +349,11 @@
"placeholder": "例如my-repo",
"helper": "运行开发服务器脚本的目录,相对于工作区根目录。留空则从工作区根目录运行。"
},
"agentWorkingDir": {
"label": "代理工作目录",
"placeholder": "例如my-repo",
"helper": "新工作区运行编码代理的默认目录,相对于工作区根目录。此值在创建工作区时保存,不会影响现有工作区。对于单仓库项目,默认为仓库名称。留空则从工作区根目录运行。"
},
"cleanup": {
"label": "清理脚本",
"helper": "此脚本从工作树内部运行,在编码代理执行后执行(仅在进行了更改时)。用于质量保证任务,如运行 linter、格式化程序、测试或其他验证步骤。如果没有进行更改则跳过此脚本。"

View File

@@ -37,6 +37,7 @@ interface ProjectFormState {
name: string;
dev_script: string;
dev_script_working_dir: string;
default_agent_working_dir: string;
}
interface RepoScriptsFormState {
@@ -51,6 +52,7 @@ function projectToFormState(project: Project): ProjectFormState {
name: project.name,
dev_script: project.dev_script ?? '',
dev_script_working_dir: project.dev_script_working_dir ?? '',
default_agent_working_dir: project.default_agent_working_dir ?? '',
};
}
@@ -393,6 +395,8 @@ export function ProjectSettings() {
name: draft.name.trim(),
dev_script: draft.dev_script.trim() || null,
dev_script_working_dir: draft.dev_script_working_dir.trim() || null,
default_agent_working_dir:
draft.default_agent_working_dir.trim() || null,
};
updateProject.mutate({
@@ -606,6 +610,26 @@ export function ProjectSettings() {
</p>
</div>
<div className="space-y-2">
<Label htmlFor="agent-working-dir">
{t('settings.projects.scripts.agentWorkingDir.label')}
</Label>
<Input
id="agent-working-dir"
value={draft.default_agent_working_dir}
onChange={(e) =>
updateDraft({ default_agent_working_dir: e.target.value })
}
placeholder={t(
'settings.projects.scripts.agentWorkingDir.placeholder'
)}
className="font-mono"
/>
<p className="text-sm text-muted-foreground">
{t('settings.projects.scripts.agentWorkingDir.helper')}
</p>
</div>
{/* Save Button */}
<div className="flex items-center justify-between pt-4 border-t">
{hasUnsavedProjectChanges ? (