Run setup script in parallel with coding agent (vibe-kanban) (#1446)
* The implementation is complete. Here's a summary of all the changes made: ## Summary ### Files Created - `crates/db/migrations/20251206000000_add_parallel_setup_script_to_projects.sql` - Database migration ### Files Modified **Backend (Rust):** - `crates/db/src/models/project.rs` - Added `parallel_setup_script: bool` field to `Project`, `Option<bool>` to `CreateProject`/`UpdateProject`, updated all SQL queries - `crates/services/src/services/container.rs` - Modified `start_attempt()` to support parallel execution when `parallel_setup_script` is true - `crates/server/src/routes/projects.rs` - Updated create/update handlers to include new field - `crates/deployment/src/lib.rs` - Added field to auto-created projects **Frontend (TypeScript/React):** - `frontend/src/pages/settings/ProjectSettings.tsx` - Added checkbox UI for parallel setup script setting - `frontend/src/components/dialogs/projects/ProjectFormDialog.tsx` - Added field to create data - `frontend/src/components/tasks/TaskDetails/preview/NoServerContent.tsx` - Added field to update data **i18n:** - `frontend/src/i18n/locales/en/settings.json` - Added translation strings - `frontend/src/i18n/locales/es/settings.json` - Added Spanish translations - `frontend/src/i18n/locales/ja/settings.json` - Added Japanese translations - `frontend/src/i18n/locales/ko/settings.json` - Added Korean translations **SQLx Cache:** - `crates/db/.sqlx/` - Updated query cache files ### Behavior - When `parallel_setup_script` is `false` (default): Sequential execution (setup → coding agent → cleanup) - When `parallel_setup_script` is `true`: Setup script and coding agent start simultaneously, setup script failure does not stop the coding agent * update useConversationHistory to display multiple running conversation threads at once * The bug fix is complete. Here's a summary of the changes: ## Bug Fix Summary **Problem:** When running in parallel mode, if the setup script finishes before the coding agent, the task incorrectly moves to "InReview" status while the coding agent is still running. **Root Cause:** The `should_finalize()` method returned `true` when `next_action.is_none()`, which is the case for setup scripts in parallel mode. This caused premature task finalization. **Solution:** Modified `should_finalize()` to check if there are other running non-DevServer processes for the same task attempt before finalizing. ### Files Modified 1. **`crates/db/src/models/execution_process.rs`** - Added `has_running_non_dev_server_processes_excluding()` method that accepts an optional `exclude_id` parameter - Refactored existing `has_running_non_dev_server_processes()` to delegate to the new method 2. **`crates/services/src/services/container.rs`** - Changed `should_finalize()` from sync to async - Added check for other running processes before allowing finalization 3. **`crates/local-deployment/src/container.rs`** - Updated call site to use `.await` on the now-async `should_finalize()` * The bug fix is complete. Here's a summary of the changes: **Fixed files:** 1. **`crates/services/src/services/container.rs`** - Simplified `should_finalize()`: - Changed from `async fn` back to `fn` (non-async) - Removed the complex database check for other running processes - Added a simple check: if the run_reason is `SetupScript` AND `next_action.is_none()` (parallel mode), return `false` (don't finalize) 2. **`crates/local-deployment/src/container.rs`** - Removed `.await` from the `should_finalize()` call 3. **`crates/db/src/models/execution_process.rs`** - Cleaned up by removing the unused `has_running_non_dev_server_processes_excluding()` function and simplifying `has_running_non_dev_server_processes()` back to its original form **How the fix works:** - In **parallel mode**: Setup scripts have `next_action: None`, so the new check catches this case and prevents finalization - In **sequential mode**: Setup scripts have `next_action: Some(coding_agent)`, so they pass this check but won't finalize anyway because `next_action.is_none()` returns `false`
This commit is contained in:
committed by
GitHub
parent
7da884bc3a
commit
76877ea631
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT id as \"id!: Uuid\",\n name,\n git_repo_path,\n setup_script,\n dev_script,\n cleanup_script,\n copy_files,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM projects\n WHERE git_repo_path = $1 AND id != $2",
|
||||
"query": "SELECT id as \"id!: Uuid\",\n name,\n git_repo_path,\n setup_script,\n dev_script,\n cleanup_script,\n copy_files,\n parallel_setup_script as \"parallel_setup_script!: bool\",\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM projects\n WHERE git_repo_path = $1 AND id != $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -39,18 +39,23 @@
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "remote_project_id: Uuid",
|
||||
"name": "parallel_setup_script!: bool",
|
||||
"ordinal": 7,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "remote_project_id: Uuid",
|
||||
"ordinal": 8,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "created_at!: DateTime<Utc>",
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "updated_at!: DateTime<Utc>",
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
@@ -65,10 +70,11 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "3c370bbd5b58c1e5de1ca4799c7fe2b3202173a9211c2d1493d79d93493754a3"
|
||||
"hash": "1394d6317296aa95af82b78e038e40a929ad163d4986208bfebb243ed7372199"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT id as \"id!: Uuid\",\n name,\n git_repo_path,\n setup_script,\n dev_script,\n cleanup_script,\n copy_files,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM projects\n WHERE id = $1",
|
||||
"query": "SELECT id as \"id!: Uuid\",\n name,\n git_repo_path,\n setup_script,\n dev_script,\n cleanup_script,\n copy_files,\n parallel_setup_script as \"parallel_setup_script!: bool\",\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM projects\n WHERE remote_project_id = $1\n LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -39,18 +39,23 @@
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "remote_project_id: Uuid",
|
||||
"name": "parallel_setup_script!: bool",
|
||||
"ordinal": 7,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "remote_project_id: Uuid",
|
||||
"ordinal": 8,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "created_at!: DateTime<Utc>",
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "updated_at!: DateTime<Utc>",
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
@@ -65,10 +70,11 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "a6ee0cb1535be5f414429a26c1534afa3f859f87c291b33769049b922ab8ff86"
|
||||
"hash": "46139a0b9debe5889e7d35b3cf9b20edec27573bb555a66fbb27cd78161392af"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "UPDATE projects\n SET name = $2,\n git_repo_path = $3,\n setup_script = $4,\n dev_script = $5,\n cleanup_script = $6,\n copy_files = $7\n WHERE id = $1\n RETURNING id as \"id!: Uuid\",\n name,\n git_repo_path,\n setup_script,\n dev_script,\n cleanup_script,\n copy_files,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"",
|
||||
"query": "UPDATE projects\n SET name = $2,\n git_repo_path = $3,\n setup_script = $4,\n dev_script = $5,\n cleanup_script = $6,\n copy_files = $7,\n parallel_setup_script = $8\n WHERE id = $1\n RETURNING id as \"id!: Uuid\",\n name,\n git_repo_path,\n setup_script,\n dev_script,\n cleanup_script,\n copy_files,\n parallel_setup_script as \"parallel_setup_script!: bool\",\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -39,23 +39,28 @@
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "remote_project_id: Uuid",
|
||||
"name": "parallel_setup_script!: bool",
|
||||
"ordinal": 7,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "remote_project_id: Uuid",
|
||||
"ordinal": 8,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "created_at!: DateTime<Utc>",
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "updated_at!: DateTime<Utc>",
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 7
|
||||
"Right": 8
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
@@ -65,10 +70,11 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "2d49b016e3d5872a71d07525a9d15637c9799e8918f125058413028ecb931a5c"
|
||||
"hash": "59f5b5a5d4ec2a614c039fb84ecf5081e7ec23ef0fef7cfe2b60ec9fc9d69d18"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT id as \"id!: Uuid\",\n name,\n git_repo_path,\n setup_script,\n dev_script,\n cleanup_script,\n copy_files,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM projects\n WHERE remote_project_id = $1\n LIMIT 1",
|
||||
"query": "SELECT id as \"id!: Uuid\",\n name,\n git_repo_path,\n setup_script,\n dev_script,\n cleanup_script,\n copy_files,\n parallel_setup_script as \"parallel_setup_script!: bool\",\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM projects\n WHERE git_repo_path = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -39,18 +39,23 @@
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "remote_project_id: Uuid",
|
||||
"name": "parallel_setup_script!: bool",
|
||||
"ordinal": 7,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "remote_project_id: Uuid",
|
||||
"ordinal": 8,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "created_at!: DateTime<Utc>",
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "updated_at!: DateTime<Utc>",
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
@@ -65,10 +70,11 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "2330097afa4816aaf7d98e083eac80558ecb9a355384e5076aa744fab27d2f7e"
|
||||
"hash": "5ab2baf9bee3fb408b31e8ce408220b741ca5e6fac93e71f93a14fb345c6c704"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT id as \"id!: Uuid\",\n name,\n git_repo_path,\n setup_script,\n dev_script,\n cleanup_script,\n copy_files,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM projects\n ORDER BY created_at DESC",
|
||||
"query": "SELECT id as \"id!: Uuid\",\n name,\n git_repo_path,\n setup_script,\n dev_script,\n cleanup_script,\n copy_files,\n parallel_setup_script as \"parallel_setup_script!: bool\",\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM projects\n ORDER BY created_at DESC",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -39,18 +39,23 @@
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "remote_project_id: Uuid",
|
||||
"name": "parallel_setup_script!: bool",
|
||||
"ordinal": 7,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "remote_project_id: Uuid",
|
||||
"ordinal": 8,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "created_at!: DateTime<Utc>",
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "updated_at!: DateTime<Utc>",
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
@@ -65,10 +70,11 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "24fc0f4f51e4080aebf6131c47eb241ef5c35440b23cfa712311692143be53f3"
|
||||
"hash": "5ae74dcee62a835018743862b2beceb7231696a0775ba2f144a0b1ecc8bfd56d"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "INSERT INTO projects (\n id,\n name,\n git_repo_path,\n setup_script,\n dev_script,\n cleanup_script,\n copy_files\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7\n )\n RETURNING id as \"id!: Uuid\",\n name,\n git_repo_path,\n setup_script,\n dev_script,\n cleanup_script,\n copy_files,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"",
|
||||
"query": "INSERT INTO projects (\n id,\n name,\n git_repo_path,\n setup_script,\n dev_script,\n cleanup_script,\n copy_files,\n parallel_setup_script\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8\n )\n RETURNING id as \"id!: Uuid\",\n name,\n git_repo_path,\n setup_script,\n dev_script,\n cleanup_script,\n copy_files,\n parallel_setup_script as \"parallel_setup_script!: bool\",\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -39,23 +39,28 @@
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "remote_project_id: Uuid",
|
||||
"name": "parallel_setup_script!: bool",
|
||||
"ordinal": 7,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "remote_project_id: Uuid",
|
||||
"ordinal": 8,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "created_at!: DateTime<Utc>",
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "updated_at!: DateTime<Utc>",
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 7
|
||||
"Right": 8
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
@@ -65,10 +70,11 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "18a4eb409f5d5ea419c98fabcfaa024126074d3b29202195c6e3b12a75c32338"
|
||||
"hash": "921d5710f2e19167d69b60da029246dfce714f1fc57d6625dd752b7aa644b497"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT id as \"id!: Uuid\",\n name,\n git_repo_path,\n setup_script,\n dev_script,\n cleanup_script,\n copy_files,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM projects\n WHERE git_repo_path = $1",
|
||||
"query": "SELECT id as \"id!: Uuid\",\n name,\n git_repo_path,\n setup_script,\n dev_script,\n cleanup_script,\n copy_files,\n parallel_setup_script as \"parallel_setup_script!: bool\",\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM projects\n WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -39,18 +39,23 @@
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "remote_project_id: Uuid",
|
||||
"name": "parallel_setup_script!: bool",
|
||||
"ordinal": 7,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "remote_project_id: Uuid",
|
||||
"ordinal": 8,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "created_at!: DateTime<Utc>",
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "updated_at!: DateTime<Utc>",
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
@@ -65,10 +70,11 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "c53e0af00938e45ba437e81cdb6c3e3d5d0ccaf7122c3830d9935dd10111ea70"
|
||||
"hash": "cff3d8c29c20cb7ddf3c9ba4c7f8a1191a60dacd760f5c711a947b3454735945"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT p.id as \"id!: Uuid\", p.name, p.git_repo_path, p.setup_script, p.dev_script, p.cleanup_script, p.copy_files, \n p.remote_project_id as \"remote_project_id: Uuid\",\n p.created_at as \"created_at!: DateTime<Utc>\", p.updated_at as \"updated_at!: DateTime<Utc>\"\n FROM projects p\n WHERE p.id IN (\n SELECT DISTINCT t.project_id\n FROM tasks t\n INNER JOIN task_attempts ta ON ta.task_id = t.id\n ORDER BY ta.updated_at DESC\n )\n LIMIT $1\n ",
|
||||
"query": "\n SELECT p.id as \"id!: Uuid\", p.name, p.git_repo_path, p.setup_script, p.dev_script, p.cleanup_script, p.copy_files,\n p.parallel_setup_script as \"parallel_setup_script!: bool\",\n p.remote_project_id as \"remote_project_id: Uuid\",\n p.created_at as \"created_at!: DateTime<Utc>\", p.updated_at as \"updated_at!: DateTime<Utc>\"\n FROM projects p\n WHERE p.id IN (\n SELECT DISTINCT t.project_id\n FROM tasks t\n INNER JOIN task_attempts ta ON ta.task_id = t.id\n ORDER BY ta.updated_at DESC\n )\n LIMIT $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -39,18 +39,23 @@
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "remote_project_id: Uuid",
|
||||
"name": "parallel_setup_script!: bool",
|
||||
"ordinal": 7,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "remote_project_id: Uuid",
|
||||
"ordinal": 8,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "created_at!: DateTime<Utc>",
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "updated_at!: DateTime<Utc>",
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
@@ -65,10 +70,11 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "4c8cc854d7f9ff93fb86a5a1a99cb99c86c50e062281bf3e52e2ebc6537192f0"
|
||||
"hash": "e82f7fc1672ff56df544cc59fec2dba6d739d11567965be2cf3a1ca99beccae2"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add parallel_setup_script column to projects table
|
||||
-- When true, setup script runs in parallel with coding agent instead of sequentially
|
||||
ALTER TABLE projects ADD COLUMN parallel_setup_script INTEGER NOT NULL DEFAULT 0;
|
||||
@@ -30,6 +30,7 @@ pub struct Project {
|
||||
pub dev_script: Option<String>,
|
||||
pub cleanup_script: Option<String>,
|
||||
pub copy_files: Option<String>,
|
||||
pub parallel_setup_script: bool,
|
||||
pub remote_project_id: Option<Uuid>,
|
||||
#[ts(type = "Date")]
|
||||
pub created_at: DateTime<Utc>,
|
||||
@@ -46,6 +47,7 @@ pub struct CreateProject {
|
||||
pub dev_script: Option<String>,
|
||||
pub cleanup_script: Option<String>,
|
||||
pub copy_files: Option<String>,
|
||||
pub parallel_setup_script: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, TS)]
|
||||
@@ -56,6 +58,7 @@ pub struct UpdateProject {
|
||||
pub dev_script: Option<String>,
|
||||
pub cleanup_script: Option<String>,
|
||||
pub copy_files: Option<String>,
|
||||
pub parallel_setup_script: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, TS)]
|
||||
@@ -89,6 +92,7 @@ impl Project {
|
||||
dev_script,
|
||||
cleanup_script,
|
||||
copy_files,
|
||||
parallel_setup_script as "parallel_setup_script!: bool",
|
||||
remote_project_id as "remote_project_id: Uuid",
|
||||
created_at as "created_at!: DateTime<Utc>",
|
||||
updated_at as "updated_at!: DateTime<Utc>"
|
||||
@@ -104,7 +108,8 @@ impl Project {
|
||||
sqlx::query_as!(
|
||||
Project,
|
||||
r#"
|
||||
SELECT p.id as "id!: Uuid", p.name, p.git_repo_path, p.setup_script, p.dev_script, p.cleanup_script, p.copy_files,
|
||||
SELECT p.id as "id!: Uuid", p.name, p.git_repo_path, p.setup_script, p.dev_script, p.cleanup_script, p.copy_files,
|
||||
p.parallel_setup_script as "parallel_setup_script!: bool",
|
||||
p.remote_project_id as "remote_project_id: Uuid",
|
||||
p.created_at as "created_at!: DateTime<Utc>", p.updated_at as "updated_at!: DateTime<Utc>"
|
||||
FROM projects p
|
||||
@@ -132,6 +137,7 @@ impl Project {
|
||||
dev_script,
|
||||
cleanup_script,
|
||||
copy_files,
|
||||
parallel_setup_script as "parallel_setup_script!: bool",
|
||||
remote_project_id as "remote_project_id: Uuid",
|
||||
created_at as "created_at!: DateTime<Utc>",
|
||||
updated_at as "updated_at!: DateTime<Utc>"
|
||||
@@ -156,6 +162,7 @@ impl Project {
|
||||
dev_script,
|
||||
cleanup_script,
|
||||
copy_files,
|
||||
parallel_setup_script as "parallel_setup_script!: bool",
|
||||
remote_project_id as "remote_project_id: Uuid",
|
||||
created_at as "created_at!: DateTime<Utc>",
|
||||
updated_at as "updated_at!: DateTime<Utc>"
|
||||
@@ -181,6 +188,7 @@ impl Project {
|
||||
dev_script,
|
||||
cleanup_script,
|
||||
copy_files,
|
||||
parallel_setup_script as "parallel_setup_script!: bool",
|
||||
remote_project_id as "remote_project_id: Uuid",
|
||||
created_at as "created_at!: DateTime<Utc>",
|
||||
updated_at as "updated_at!: DateTime<Utc>"
|
||||
@@ -206,6 +214,7 @@ impl Project {
|
||||
dev_script,
|
||||
cleanup_script,
|
||||
copy_files,
|
||||
parallel_setup_script as "parallel_setup_script!: bool",
|
||||
remote_project_id as "remote_project_id: Uuid",
|
||||
created_at as "created_at!: DateTime<Utc>",
|
||||
updated_at as "updated_at!: DateTime<Utc>"
|
||||
@@ -223,6 +232,7 @@ impl Project {
|
||||
data: &CreateProject,
|
||||
project_id: Uuid,
|
||||
) -> Result<Self, sqlx::Error> {
|
||||
let parallel_setup_script = data.parallel_setup_script.unwrap_or(false);
|
||||
sqlx::query_as!(
|
||||
Project,
|
||||
r#"INSERT INTO projects (
|
||||
@@ -232,9 +242,10 @@ impl Project {
|
||||
setup_script,
|
||||
dev_script,
|
||||
cleanup_script,
|
||||
copy_files
|
||||
copy_files,
|
||||
parallel_setup_script
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7
|
||||
$1, $2, $3, $4, $5, $6, $7, $8
|
||||
)
|
||||
RETURNING id as "id!: Uuid",
|
||||
name,
|
||||
@@ -243,6 +254,7 @@ impl Project {
|
||||
dev_script,
|
||||
cleanup_script,
|
||||
copy_files,
|
||||
parallel_setup_script as "parallel_setup_script!: bool",
|
||||
remote_project_id as "remote_project_id: Uuid",
|
||||
created_at as "created_at!: DateTime<Utc>",
|
||||
updated_at as "updated_at!: DateTime<Utc>""#,
|
||||
@@ -253,6 +265,7 @@ impl Project {
|
||||
data.dev_script,
|
||||
data.cleanup_script,
|
||||
data.copy_files,
|
||||
parallel_setup_script,
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
@@ -268,6 +281,7 @@ impl Project {
|
||||
dev_script: Option<String>,
|
||||
cleanup_script: Option<String>,
|
||||
copy_files: Option<String>,
|
||||
parallel_setup_script: bool,
|
||||
) -> Result<Self, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
Project,
|
||||
@@ -277,7 +291,8 @@ impl Project {
|
||||
setup_script = $4,
|
||||
dev_script = $5,
|
||||
cleanup_script = $6,
|
||||
copy_files = $7
|
||||
copy_files = $7,
|
||||
parallel_setup_script = $8
|
||||
WHERE id = $1
|
||||
RETURNING id as "id!: Uuid",
|
||||
name,
|
||||
@@ -286,6 +301,7 @@ impl Project {
|
||||
dev_script,
|
||||
cleanup_script,
|
||||
copy_files,
|
||||
parallel_setup_script as "parallel_setup_script!: bool",
|
||||
remote_project_id as "remote_project_id: Uuid",
|
||||
created_at as "created_at!: DateTime<Utc>",
|
||||
updated_at as "updated_at!: DateTime<Utc>""#,
|
||||
@@ -296,6 +312,7 @@ impl Project {
|
||||
dev_script,
|
||||
cleanup_script,
|
||||
copy_files,
|
||||
parallel_setup_script,
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
|
||||
@@ -166,6 +166,7 @@ pub trait Deployment: Clone + Send + Sync + 'static {
|
||||
dev_script: None,
|
||||
cleanup_script: None,
|
||||
copy_files: None,
|
||||
parallel_setup_script: None,
|
||||
};
|
||||
// Ensure existing repo has a main branch if it's empty
|
||||
if let Err(e) = self.git().ensure_main_branch_exists(&repo.path) {
|
||||
|
||||
@@ -205,6 +205,7 @@ pub async fn create_project(
|
||||
dev_script,
|
||||
cleanup_script,
|
||||
copy_files,
|
||||
parallel_setup_script,
|
||||
use_existing_repo,
|
||||
} = payload;
|
||||
tracing::debug!("Creating project '{}'", name);
|
||||
@@ -292,6 +293,7 @@ pub async fn create_project(
|
||||
dev_script,
|
||||
cleanup_script,
|
||||
copy_files,
|
||||
parallel_setup_script,
|
||||
},
|
||||
id,
|
||||
)
|
||||
@@ -333,6 +335,7 @@ pub async fn update_project(
|
||||
dev_script,
|
||||
cleanup_script,
|
||||
copy_files,
|
||||
parallel_setup_script,
|
||||
} = payload;
|
||||
// If git_repo_path is being changed, check if the new path is already used by another project
|
||||
let git_repo_path = if let Some(new_git_repo_path) = git_repo_path.map(|s| expand_tilde(&s))
|
||||
@@ -369,6 +372,7 @@ pub async fn update_project(
|
||||
dev_script,
|
||||
cleanup_script,
|
||||
copy_files,
|
||||
parallel_setup_script.unwrap_or(existing_project.parallel_setup_script),
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -123,14 +123,29 @@ pub trait ContainerService {
|
||||
/// A context is finalized when
|
||||
/// - Always when the execution process has failed or been killed
|
||||
/// - Never when the run reason is DevServer
|
||||
/// - Never when a setup script has no next_action (parallel mode)
|
||||
/// - The next action is None (no follow-up actions)
|
||||
fn should_finalize(&self, ctx: &ExecutionContext) -> bool {
|
||||
// Never finalize DevServer processes
|
||||
if matches!(
|
||||
ctx.execution_process.run_reason,
|
||||
ExecutionProcessRunReason::DevServer
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Never finalize setup scripts without a next_action (parallel mode).
|
||||
// In sequential mode, setup scripts have next_action pointing to coding agent,
|
||||
// so they won't finalize anyway (handled by next_action.is_none() check below).
|
||||
let action = ctx.execution_process.executor_action().unwrap();
|
||||
if matches!(
|
||||
ctx.execution_process.run_reason,
|
||||
ExecutionProcessRunReason::SetupScript
|
||||
) && action.next_action.is_none()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Always finalize failed or killed executions, regardless of next action
|
||||
if matches!(
|
||||
ctx.execution_process.status,
|
||||
@@ -138,12 +153,9 @@ pub trait ContainerService {
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, finalize only if no next action
|
||||
ctx.execution_process
|
||||
.executor_action()
|
||||
.unwrap()
|
||||
.next_action
|
||||
.is_none()
|
||||
action.next_action.is_none()
|
||||
}
|
||||
|
||||
/// Finalize task execution by updating status to InReview and sending notifications
|
||||
@@ -670,32 +682,74 @@ pub trait ContainerService {
|
||||
|
||||
let prompt = task.to_prompt();
|
||||
|
||||
let cleanup_action = self.cleanup_action(project.cleanup_script);
|
||||
let cleanup_action = self.cleanup_action(project.cleanup_script.clone());
|
||||
|
||||
// Choose whether to execute the setup_script or coding agent first
|
||||
let execution_process = if let Some(setup_script) = project.setup_script {
|
||||
let executor_action = ExecutorAction::new(
|
||||
ExecutorActionType::ScriptRequest(ScriptRequest {
|
||||
script: setup_script,
|
||||
language: ScriptRequestLanguage::Bash,
|
||||
context: ScriptContext::SetupScript,
|
||||
}),
|
||||
// once the setup script is done, run the initial coding agent request
|
||||
Some(Box::new(ExecutorAction::new(
|
||||
if project.parallel_setup_script {
|
||||
// PARALLEL EXECUTION: Start setup script and coding agent independently
|
||||
// Setup script runs without next_action (it completes on its own)
|
||||
let setup_action = ExecutorAction::new(
|
||||
ExecutorActionType::ScriptRequest(ScriptRequest {
|
||||
script: setup_script,
|
||||
language: ScriptRequestLanguage::Bash,
|
||||
context: ScriptContext::SetupScript,
|
||||
}),
|
||||
None, // No chaining - runs independently
|
||||
);
|
||||
|
||||
// Start setup script (ignore errors - coding agent will start regardless)
|
||||
if let Err(e) = self
|
||||
.start_execution(
|
||||
&task_attempt,
|
||||
&setup_action,
|
||||
&ExecutionProcessRunReason::SetupScript,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::warn!(?e, "Failed to start setup script in parallel mode");
|
||||
}
|
||||
|
||||
// Start coding agent independently with cleanup as next_action
|
||||
let coding_action = ExecutorAction::new(
|
||||
ExecutorActionType::CodingAgentInitialRequest(CodingAgentInitialRequest {
|
||||
prompt,
|
||||
executor_profile_id: executor_profile_id.clone(),
|
||||
}),
|
||||
cleanup_action,
|
||||
))),
|
||||
);
|
||||
);
|
||||
|
||||
self.start_execution(
|
||||
&task_attempt,
|
||||
&executor_action,
|
||||
&ExecutionProcessRunReason::SetupScript,
|
||||
)
|
||||
.await?
|
||||
self.start_execution(
|
||||
&task_attempt,
|
||||
&coding_action,
|
||||
&ExecutionProcessRunReason::CodingAgent,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
// SEQUENTIAL EXECUTION: Setup script runs first, then coding agent
|
||||
let executor_action = ExecutorAction::new(
|
||||
ExecutorActionType::ScriptRequest(ScriptRequest {
|
||||
script: setup_script,
|
||||
language: ScriptRequestLanguage::Bash,
|
||||
context: ScriptContext::SetupScript,
|
||||
}),
|
||||
// once the setup script is done, run the initial coding agent request
|
||||
Some(Box::new(ExecutorAction::new(
|
||||
ExecutorActionType::CodingAgentInitialRequest(CodingAgentInitialRequest {
|
||||
prompt,
|
||||
executor_profile_id: executor_profile_id.clone(),
|
||||
}),
|
||||
cleanup_action,
|
||||
))),
|
||||
);
|
||||
|
||||
self.start_execution(
|
||||
&task_attempt,
|
||||
&executor_action,
|
||||
&ExecutionProcessRunReason::SetupScript,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
} else {
|
||||
let executor_action = ExecutorAction::new(
|
||||
ExecutorActionType::CodingAgentInitialRequest(CodingAgentInitialRequest {
|
||||
|
||||
Binary file not shown.
@@ -51,6 +51,7 @@ const ProjectFormDialogImpl = NiceModal.create<ProjectFormDialogProps>(() => {
|
||||
dev_script: null,
|
||||
cleanup_script: null,
|
||||
copy_files: null,
|
||||
parallel_setup_script: null,
|
||||
};
|
||||
|
||||
createProject.mutate(createData);
|
||||
@@ -81,6 +82,7 @@ const ProjectFormDialogImpl = NiceModal.create<ProjectFormDialogProps>(() => {
|
||||
dev_script: null,
|
||||
cleanup_script: null,
|
||||
copy_files: null,
|
||||
parallel_setup_script: null,
|
||||
};
|
||||
|
||||
createProject.mutate(createData);
|
||||
|
||||
@@ -94,6 +94,7 @@ export function NoServerContent({
|
||||
dev_script: script,
|
||||
cleanup_script: project.cleanup_script ?? null,
|
||||
copy_files: project.copy_files ?? null,
|
||||
parallel_setup_script: project.parallel_setup_script ?? null,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -50,7 +50,7 @@ interface UseConversationHistoryResult {}
|
||||
const MIN_INITIAL_ENTRIES = 10;
|
||||
const REMAINING_BATCH_SIZE = 50;
|
||||
|
||||
const loadingPatch: PatchTypeWithKey = {
|
||||
const makeLoadingPatch = (executionProcessId: string): PatchTypeWithKey => ({
|
||||
type: 'NORMALIZED_ENTRY',
|
||||
content: {
|
||||
entry_type: {
|
||||
@@ -59,9 +59,9 @@ const loadingPatch: PatchTypeWithKey = {
|
||||
content: '',
|
||||
timestamp: null,
|
||||
},
|
||||
patchKey: 'loading',
|
||||
executionProcessId: '',
|
||||
};
|
||||
patchKey: `${executionProcessId}:loading`,
|
||||
executionProcessId,
|
||||
});
|
||||
|
||||
const nextActionPatch: (
|
||||
failed: boolean,
|
||||
@@ -99,7 +99,7 @@ export const useConversationHistory = ({
|
||||
const executionProcesses = useRef<ExecutionProcess[]>(executionProcessesRaw);
|
||||
const displayedExecutionProcesses = useRef<ExecutionProcessStateStore>({});
|
||||
const loadedInitialEntries = useRef(false);
|
||||
const lastActiveProcessId = useRef<string | null>(null);
|
||||
const streamingProcessIdsRef = useRef<Set<string>>(new Set());
|
||||
const onEntriesUpdatedRef = useRef<OnEntriesUpdated | null>(null);
|
||||
|
||||
const mergeIntoDisplayed = (
|
||||
@@ -191,16 +191,14 @@ export const useConversationHistory = ({
|
||||
.flatMap((p) => p.entries);
|
||||
};
|
||||
|
||||
const getActiveAgentProcess = (): ExecutionProcess | null => {
|
||||
const activeProcesses = executionProcesses?.current.filter(
|
||||
(p) =>
|
||||
p.status === ExecutionProcessStatus.running &&
|
||||
p.run_reason !== 'devserver'
|
||||
const getActiveAgentProcesses = (): ExecutionProcess[] => {
|
||||
return (
|
||||
executionProcesses?.current.filter(
|
||||
(p) =>
|
||||
p.status === ExecutionProcessStatus.running &&
|
||||
p.run_reason !== 'devserver'
|
||||
) ?? []
|
||||
);
|
||||
if (activeProcesses.length > 1) {
|
||||
console.error('More than one active execution process found');
|
||||
}
|
||||
return activeProcesses[0] || null;
|
||||
};
|
||||
|
||||
const flattenEntriesForEmit = useCallback(
|
||||
@@ -312,7 +310,7 @@ export const useConversationHistory = ({
|
||||
}
|
||||
|
||||
if (isProcessRunning && !hasPendingApprovalEntry) {
|
||||
entries.push(loadingPatch);
|
||||
entries.push(makeLoadingPatch(p.executionProcess.id));
|
||||
}
|
||||
} else if (
|
||||
p.executionProcess.executor_action.typ.type === 'ScriptRequest'
|
||||
@@ -625,24 +623,32 @@ export const useConversationHistory = ({
|
||||
]); // include idListKey so new processes trigger reload
|
||||
|
||||
useEffect(() => {
|
||||
const activeProcess = getActiveAgentProcess();
|
||||
if (!activeProcess) return;
|
||||
const activeProcesses = getActiveAgentProcesses();
|
||||
if (activeProcesses.length === 0) return;
|
||||
|
||||
if (!displayedExecutionProcesses.current[activeProcess.id]) {
|
||||
const runningOrInitial =
|
||||
Object.keys(displayedExecutionProcesses.current).length > 1
|
||||
? 'running'
|
||||
: 'initial';
|
||||
ensureProcessVisible(activeProcess);
|
||||
emitEntries(displayedExecutionProcesses.current, runningOrInitial, false);
|
||||
}
|
||||
for (const activeProcess of activeProcesses) {
|
||||
if (!displayedExecutionProcesses.current[activeProcess.id]) {
|
||||
const runningOrInitial =
|
||||
Object.keys(displayedExecutionProcesses.current).length > 1
|
||||
? 'running'
|
||||
: 'initial';
|
||||
ensureProcessVisible(activeProcess);
|
||||
emitEntries(
|
||||
displayedExecutionProcesses.current,
|
||||
runningOrInitial,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
activeProcess.status === ExecutionProcessStatus.running &&
|
||||
lastActiveProcessId.current !== activeProcess.id
|
||||
) {
|
||||
lastActiveProcessId.current = activeProcess.id;
|
||||
loadRunningAndEmitWithBackoff(activeProcess);
|
||||
if (
|
||||
activeProcess.status === ExecutionProcessStatus.running &&
|
||||
!streamingProcessIdsRef.current.has(activeProcess.id)
|
||||
) {
|
||||
streamingProcessIdsRef.current.add(activeProcess.id);
|
||||
loadRunningAndEmitWithBackoff(activeProcess).finally(() => {
|
||||
streamingProcessIdsRef.current.delete(activeProcess.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [
|
||||
attempt.id,
|
||||
@@ -673,7 +679,7 @@ export const useConversationHistory = ({
|
||||
useEffect(() => {
|
||||
displayedExecutionProcesses.current = {};
|
||||
loadedInitialEntries.current = false;
|
||||
lastActiveProcessId.current = null;
|
||||
streamingProcessIdsRef.current.clear();
|
||||
emitEntries(displayedExecutionProcesses.current, 'initial', true);
|
||||
}, [attempt.id, emitEntries]);
|
||||
|
||||
|
||||
@@ -324,7 +324,9 @@
|
||||
"description": "Configure setup, development, and cleanup scripts for this project.",
|
||||
"setup": {
|
||||
"label": "Setup Script",
|
||||
"helper": "This script will run after creating the worktree and before the coding agent starts. Use it for setup tasks like installing dependencies or preparing the environment."
|
||||
"helper": "This script will run after creating the worktree and before the coding agent starts. Use it for setup tasks like installing dependencies or preparing the environment.",
|
||||
"parallelLabel": "Run setup script in parallel with coding agent",
|
||||
"parallelHelper": "When enabled, the setup script runs simultaneously with the coding agent instead of waiting for setup to complete first."
|
||||
},
|
||||
"dev": {
|
||||
"label": "Dev Server Script",
|
||||
|
||||
@@ -324,7 +324,9 @@
|
||||
"description": "Configura los scripts de instalación, desarrollo y limpieza para este proyecto.",
|
||||
"setup": {
|
||||
"label": "Script de Instalación",
|
||||
"helper": "Este script se ejecutará después de crear el worktree y antes de que comience el agente de codificación. Úsalo para tareas de configuración como instalar dependencias o preparar el entorno."
|
||||
"helper": "Este script se ejecutará después de crear el worktree y antes de que comience el agente de codificación. Úsalo para tareas de configuración como instalar dependencias o preparar el entorno.",
|
||||
"parallelLabel": "Ejecutar script de instalación en paralelo con el agente de codificación",
|
||||
"parallelHelper": "Cuando está habilitado, el script de instalación se ejecuta simultáneamente con el agente de codificación en lugar de esperar a que se complete la configuración primero."
|
||||
},
|
||||
"dev": {
|
||||
"label": "Script del Servidor de Desarrollo",
|
||||
|
||||
@@ -324,7 +324,9 @@
|
||||
"description": "このプロジェクトのセットアップ、開発、およびクリーンアップスクリプトを設定します。",
|
||||
"setup": {
|
||||
"label": "セットアップスクリプト",
|
||||
"helper": "このスクリプトは、ワークツリーの作成後、コーディングエージェントの開始前に実行されます。依存関係のインストールや環境の準備などのセットアップタスクに使用してください。"
|
||||
"helper": "このスクリプトは、ワークツリーの作成後、コーディングエージェントの開始前に実行されます。依存関係のインストールや環境の準備などのセットアップタスクに使用してください。",
|
||||
"parallelLabel": "セットアップスクリプトをコーディングエージェントと並行して実行",
|
||||
"parallelHelper": "有効にすると、セットアップスクリプトはセットアップの完了を待たずに、コーディングエージェントと同時に実行されます。"
|
||||
},
|
||||
"dev": {
|
||||
"label": "開発サーバースクリプト",
|
||||
|
||||
@@ -324,7 +324,9 @@
|
||||
"description": "이 프로젝트의 설정, 개발 및 정리 스크립트를 구성하세요.",
|
||||
"setup": {
|
||||
"label": "설정 스크립트",
|
||||
"helper": "이 스크립트는 워크트리를 생성한 후 코딩 에이전트가 시작되기 전에 실행됩니다. 종속성 설치 또는 환경 준비와 같은 설정 작업에 사용하세요."
|
||||
"helper": "이 스크립트는 워크트리를 생성한 후 코딩 에이전트가 시작되기 전에 실행됩니다. 종속성 설치 또는 환경 준비와 같은 설정 작업에 사용하세요.",
|
||||
"parallelLabel": "설정 스크립트를 코딩 에이전트와 병렬로 실행",
|
||||
"parallelHelper": "활성화되면 설정 스크립트가 설정 완료를 기다리지 않고 코딩 에이전트와 동시에 실행됩니다."
|
||||
},
|
||||
"dev": {
|
||||
"label": "개발 서버 스크립트",
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from '@/components/ui/select';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { Loader2, Folder } from 'lucide-react';
|
||||
import { useProjects } from '@/hooks/useProjects';
|
||||
@@ -33,6 +34,7 @@ interface ProjectFormState {
|
||||
name: string;
|
||||
git_repo_path: string;
|
||||
setup_script: string;
|
||||
parallel_setup_script: boolean;
|
||||
dev_script: string;
|
||||
cleanup_script: string;
|
||||
copy_files: string;
|
||||
@@ -43,6 +45,7 @@ function projectToFormState(project: Project): ProjectFormState {
|
||||
name: project.name,
|
||||
git_repo_path: project.git_repo_path,
|
||||
setup_script: project.setup_script ?? '',
|
||||
parallel_setup_script: project.parallel_setup_script ?? false,
|
||||
dev_script: project.dev_script ?? '',
|
||||
cleanup_script: project.cleanup_script ?? '',
|
||||
copy_files: project.copy_files ?? '',
|
||||
@@ -211,6 +214,7 @@ export function ProjectSettings() {
|
||||
name: draft.name.trim(),
|
||||
git_repo_path: draft.git_repo_path.trim(),
|
||||
setup_script: draft.setup_script.trim() || null,
|
||||
parallel_setup_script: draft.parallel_setup_script,
|
||||
dev_script: draft.dev_script.trim() || null,
|
||||
cleanup_script: draft.cleanup_script.trim() || null,
|
||||
copy_files: draft.copy_files.trim() || null,
|
||||
@@ -414,6 +418,26 @@ export function ProjectSettings() {
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('settings.projects.scripts.setup.helper')}
|
||||
</p>
|
||||
|
||||
<div className="flex items-center space-x-2 pt-2">
|
||||
<Checkbox
|
||||
id="parallel-setup-script"
|
||||
checked={draft.parallel_setup_script}
|
||||
onCheckedChange={(checked) =>
|
||||
updateDraft({ parallel_setup_script: checked === true })
|
||||
}
|
||||
disabled={!draft.setup_script.trim()}
|
||||
/>
|
||||
<Label
|
||||
htmlFor="parallel-setup-script"
|
||||
className="text-sm font-normal cursor-pointer"
|
||||
>
|
||||
{t('settings.projects.scripts.setup.parallelLabel')}
|
||||
</Label>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground pl-6">
|
||||
{t('settings.projects.scripts.setup.parallelHelper')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -12,11 +12,11 @@ export type SharedTask = { id: string, organization_id: string, project_id: stri
|
||||
|
||||
export type UserData = { user_id: string, first_name: string | null, last_name: string | null, username: string | null, };
|
||||
|
||||
export type Project = { id: string, name: string, git_repo_path: string, setup_script: string | null, dev_script: string | null, cleanup_script: string | null, copy_files: string | null, remote_project_id: string | null, created_at: Date, updated_at: Date, };
|
||||
export type Project = { id: string, name: string, git_repo_path: string, setup_script: string | null, dev_script: string | null, cleanup_script: string | null, copy_files: string | null, parallel_setup_script: boolean, remote_project_id: string | null, created_at: Date, updated_at: Date, };
|
||||
|
||||
export type CreateProject = { name: string, git_repo_path: string, use_existing_repo: boolean, setup_script: string | null, dev_script: string | null, cleanup_script: string | null, copy_files: string | null, };
|
||||
export type CreateProject = { name: string, git_repo_path: string, use_existing_repo: boolean, setup_script: string | null, dev_script: string | null, cleanup_script: string | null, copy_files: string | null, parallel_setup_script: boolean | null, };
|
||||
|
||||
export type UpdateProject = { name: string | null, git_repo_path: string | null, setup_script: string | null, dev_script: string | null, cleanup_script: string | null, copy_files: string | null, };
|
||||
export type UpdateProject = { name: string | null, git_repo_path: string | null, setup_script: string | null, dev_script: string | null, cleanup_script: string | null, copy_files: string | null, parallel_setup_script: boolean | null, };
|
||||
|
||||
export type SearchResult = { path: string, is_file: boolean, match_type: SearchMatchType, };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user