Squashed commit of the following:
commit 93babc04486e64ae55c106478d5c04a9ec891c1f Author: Louis Knight-Webb <louis@bloop.ai> Date: Tue Jun 24 01:05:31 2025 +0100 UX commit 91c93187290e4e0882018c392dd744eba7cd2193 Author: Louis Knight-Webb <louis@bloop.ai> Date: Tue Jun 24 01:03:39 2025 +0100 Update TaskDetailsPanel.tsx Follow up UI commit b66cfbfa727eb7d69b2250102712d6169a3af3b1 Author: Louis Knight-Webb <louis@bloop.ai> Date: Tue Jun 24 00:58:21 2025 +0100 Tweaks commit aa2235c56413ffe88c4ec1bf7950012c019f9455 Author: Louis Knight-Webb <louis@bloop.ai> Date: Tue Jun 24 00:34:02 2025 +0100 Add follow up endpoint commit 1b536e33c956e39881d5ddfd169d229cfba99c20 Author: Louis Knight-Webb <louis@bloop.ai> Date: Tue Jun 24 00:12:55 2025 +0100 Track executor type commit 1c5d208f62fce2ed36e04384e139884e85dcb295 Author: Louis Knight-Webb <louis@bloop.ai> Date: Mon Jun 23 16:56:58 2025 +0100 Add executor_session commit 8e305953afb71d096079587df94cf5e63c4c6a04 Author: Louis Knight-Webb <louis@bloop.ai> Date: Mon Jun 23 16:49:07 2025 +0100 Fix type issue commit bc2dcf4fd4926ca2a42d71cd429de66fd1215208 Author: Louis Knight-Webb <louis@bloop.ai> Date: Mon Jun 23 16:03:27 2025 +0100 Refactor
This commit is contained in:
56
backend/.sqlx/query-0468aa522ed7fd2675bcf278f6be38ce16752cb73adf0fafc5b497a88f32f531.json
generated
Normal file
56
backend/.sqlx/query-0468aa522ed7fd2675bcf278f6be38ce16752cb73adf0fafc5b497a88f32f531.json
generated
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "SELECT \n id as \"id!: Uuid\", \n task_attempt_id as \"task_attempt_id!: Uuid\", \n execution_process_id as \"execution_process_id!: Uuid\", \n session_id, \n prompt,\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM executor_sessions \n WHERE execution_process_id = $1",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id!: Uuid",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Blob"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "task_attempt_id!: Uuid",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Blob"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "execution_process_id!: Uuid",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Blob"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "session_id",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "prompt",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "0468aa522ed7fd2675bcf278f6be38ce16752cb73adf0fafc5b497a88f32f531"
|
||||||
|
}
|
||||||
56
backend/.sqlx/query-06ca282915d0db9125769b1bca92f7a5bd7f81ad8faf0f9fcbb5f1c2d35dd67f.json
generated
Normal file
56
backend/.sqlx/query-06ca282915d0db9125769b1bca92f7a5bd7f81ad8faf0f9fcbb5f1c2d35dd67f.json
generated
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "SELECT \n id as \"id!: Uuid\", \n task_attempt_id as \"task_attempt_id!: Uuid\", \n execution_process_id as \"execution_process_id!: Uuid\", \n session_id, \n prompt,\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM executor_sessions \n WHERE task_attempt_id = $1 \n ORDER BY created_at ASC",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id!: Uuid",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Blob"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "task_attempt_id!: Uuid",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Blob"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "execution_process_id!: Uuid",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Blob"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "session_id",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "prompt",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "06ca282915d0db9125769b1bca92f7a5bd7f81ad8faf0f9fcbb5f1c2d35dd67f"
|
||||||
|
}
|
||||||
12
backend/.sqlx/query-1b082630a9622f8667ee7a9aba2c2d3176019a68c6bb83d33008594821415a57.json
generated
Normal file
12
backend/.sqlx/query-1b082630a9622f8667ee7a9aba2c2d3176019a68c6bb83d33008594821415a57.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "DELETE FROM executor_sessions WHERE task_attempt_id = $1",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "1b082630a9622f8667ee7a9aba2c2d3176019a68c6bb83d33008594821415a57"
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "SELECT \n id as \"id!: Uuid\", \n task_attempt_id as \"task_attempt_id!: Uuid\", \n process_type as \"process_type!: ExecutionProcessType\",\n status as \"status!: ExecutionProcessStatus\",\n command, \n args, \n working_directory, \n stdout, \n stderr, \n exit_code,\n started_at as \"started_at!: DateTime<Utc>\",\n completed_at as \"completed_at?: DateTime<Utc>\",\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM execution_processes \n WHERE status = 'running' \n ORDER BY created_at ASC",
|
"query": "SELECT \n id as \"id!: Uuid\", \n task_attempt_id as \"task_attempt_id!: Uuid\", \n process_type as \"process_type!: ExecutionProcessType\",\n executor_type,\n status as \"status!: ExecutionProcessStatus\",\n command, \n args, \n working_directory, \n stdout, \n stderr, \n exit_code,\n started_at as \"started_at!: DateTime<Utc>\",\n completed_at as \"completed_at?: DateTime<Utc>\",\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM execution_processes \n WHERE status = 'running' \n ORDER BY created_at ASC",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -19,59 +19,64 @@
|
|||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "status!: ExecutionProcessStatus",
|
"name": "executor_type",
|
||||||
"ordinal": 3,
|
"ordinal": 3,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "command",
|
"name": "status!: ExecutionProcessStatus",
|
||||||
"ordinal": 4,
|
"ordinal": 4,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "args",
|
"name": "command",
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "working_directory",
|
"name": "args",
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "stdout",
|
"name": "working_directory",
|
||||||
"ordinal": 7,
|
"ordinal": 7,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "stderr",
|
"name": "stdout",
|
||||||
"ordinal": 8,
|
"ordinal": 8,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "exit_code",
|
"name": "stderr",
|
||||||
"ordinal": 9,
|
"ordinal": 9,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "exit_code",
|
||||||
|
"ordinal": 10,
|
||||||
"type_info": "Integer"
|
"type_info": "Integer"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "started_at!: DateTime<Utc>",
|
"name": "started_at!: DateTime<Utc>",
|
||||||
"ordinal": 10,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "completed_at?: DateTime<Utc>",
|
|
||||||
"ordinal": 11,
|
"ordinal": 11,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "created_at!: DateTime<Utc>",
|
"name": "completed_at?: DateTime<Utc>",
|
||||||
"ordinal": 12,
|
"ordinal": 12,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "updated_at!: DateTime<Utc>",
|
"name": "created_at!: DateTime<Utc>",
|
||||||
"ordinal": 13,
|
"ordinal": 13,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 14,
|
||||||
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -81,6 +86,7 @@
|
|||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
@@ -94,5 +100,5 @@
|
|||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "d25396768d88ecab6e13ad9fca8e8c46e92ff17474ebd24657384e130c49afa8"
|
"hash": "1f619f01f46859a64ded531dd0ef61abacfe62e758abe7030a6aa745140b95ca"
|
||||||
}
|
}
|
||||||
56
backend/.sqlx/query-3b65c0f6215229f3c8d487c204bca5a1a8e327d9b469b47d833befa95377dfab.json
generated
Normal file
56
backend/.sqlx/query-3b65c0f6215229f3c8d487c204bca5a1a8e327d9b469b47d833befa95377dfab.json
generated
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "SELECT \n id as \"id!: Uuid\", \n task_attempt_id as \"task_attempt_id!: Uuid\", \n execution_process_id as \"execution_process_id!: Uuid\", \n session_id, \n prompt,\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM executor_sessions \n WHERE id = $1",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id!: Uuid",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Blob"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "task_attempt_id!: Uuid",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Blob"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "execution_process_id!: Uuid",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Blob"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "session_id",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "prompt",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "3b65c0f6215229f3c8d487c204bca5a1a8e327d9b469b47d833befa95377dfab"
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "INSERT INTO execution_processes (\n id, task_attempt_id, process_type, status, command, args, \n working_directory, stdout, stderr, exit_code, started_at, \n completed_at, created_at, updated_at\n ) \n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) \n RETURNING \n id as \"id!: Uuid\", \n task_attempt_id as \"task_attempt_id!: Uuid\", \n process_type as \"process_type!: ExecutionProcessType\",\n status as \"status!: ExecutionProcessStatus\",\n command, \n args, \n working_directory, \n stdout, \n stderr, \n exit_code,\n started_at as \"started_at!: DateTime<Utc>\",\n completed_at as \"completed_at?: DateTime<Utc>\",\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"",
|
"query": "INSERT INTO execution_processes (\n id, task_attempt_id, process_type, executor_type, status, command, args, \n working_directory, stdout, stderr, exit_code, started_at, \n completed_at, created_at, updated_at\n ) \n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) \n RETURNING \n id as \"id!: Uuid\", \n task_attempt_id as \"task_attempt_id!: Uuid\", \n process_type as \"process_type!: ExecutionProcessType\",\n executor_type,\n status as \"status!: ExecutionProcessStatus\",\n command, \n args, \n working_directory, \n stdout, \n stderr, \n exit_code,\n started_at as \"started_at!: DateTime<Utc>\",\n completed_at as \"completed_at?: DateTime<Utc>\",\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -19,68 +19,74 @@
|
|||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "status!: ExecutionProcessStatus",
|
"name": "executor_type",
|
||||||
"ordinal": 3,
|
"ordinal": 3,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "command",
|
"name": "status!: ExecutionProcessStatus",
|
||||||
"ordinal": 4,
|
"ordinal": 4,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "args",
|
"name": "command",
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "working_directory",
|
"name": "args",
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "stdout",
|
"name": "working_directory",
|
||||||
"ordinal": 7,
|
"ordinal": 7,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "stderr",
|
"name": "stdout",
|
||||||
"ordinal": 8,
|
"ordinal": 8,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "exit_code",
|
"name": "stderr",
|
||||||
"ordinal": 9,
|
"ordinal": 9,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "exit_code",
|
||||||
|
"ordinal": 10,
|
||||||
"type_info": "Integer"
|
"type_info": "Integer"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "started_at!: DateTime<Utc>",
|
"name": "started_at!: DateTime<Utc>",
|
||||||
"ordinal": 10,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "completed_at?: DateTime<Utc>",
|
|
||||||
"ordinal": 11,
|
"ordinal": 11,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "created_at!: DateTime<Utc>",
|
"name": "completed_at?: DateTime<Utc>",
|
||||||
"ordinal": 12,
|
"ordinal": 12,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "updated_at!: DateTime<Utc>",
|
"name": "created_at!: DateTime<Utc>",
|
||||||
"ordinal": 13,
|
"ordinal": 13,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 14,
|
||||||
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 14
|
"Right": 15
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
@@ -94,5 +100,5 @@
|
|||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "b50af42f635dec3167508f3c4f81d03911102a603ac94b22a431a513d36471b0"
|
"hash": "5ed1238e52e59bb5f76c0f153fd99a14093f7ce2585bf9843585608f17ec575b"
|
||||||
}
|
}
|
||||||
12
backend/.sqlx/query-77857828471571c0584bfed1641c4cfe15dba6dcacbb9e9bc10228a8af6da619.json
generated
Normal file
12
backend/.sqlx/query-77857828471571c0584bfed1641c4cfe15dba6dcacbb9e9bc10228a8af6da619.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "UPDATE executor_sessions \n SET session_id = $1, updated_at = datetime('now') \n WHERE execution_process_id = $2",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "77857828471571c0584bfed1641c4cfe15dba6dcacbb9e9bc10228a8af6da619"
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "SELECT \n id as \"id!: Uuid\", \n task_attempt_id as \"task_attempt_id!: Uuid\", \n process_type as \"process_type!: ExecutionProcessType\",\n status as \"status!: ExecutionProcessStatus\",\n command, \n args, \n working_directory, \n stdout, \n stderr, \n exit_code,\n started_at as \"started_at!: DateTime<Utc>\",\n completed_at as \"completed_at?: DateTime<Utc>\",\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM execution_processes \n WHERE id = $1",
|
"query": "SELECT \n id as \"id!: Uuid\", \n task_attempt_id as \"task_attempt_id!: Uuid\", \n process_type as \"process_type!: ExecutionProcessType\",\n executor_type,\n status as \"status!: ExecutionProcessStatus\",\n command, \n args, \n working_directory, \n stdout, \n stderr, \n exit_code,\n started_at as \"started_at!: DateTime<Utc>\",\n completed_at as \"completed_at?: DateTime<Utc>\",\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM execution_processes \n WHERE task_attempt_id = $1 \n ORDER BY created_at ASC",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -19,59 +19,64 @@
|
|||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "status!: ExecutionProcessStatus",
|
"name": "executor_type",
|
||||||
"ordinal": 3,
|
"ordinal": 3,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "command",
|
"name": "status!: ExecutionProcessStatus",
|
||||||
"ordinal": 4,
|
"ordinal": 4,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "args",
|
"name": "command",
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "working_directory",
|
"name": "args",
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "stdout",
|
"name": "working_directory",
|
||||||
"ordinal": 7,
|
"ordinal": 7,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "stderr",
|
"name": "stdout",
|
||||||
"ordinal": 8,
|
"ordinal": 8,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "exit_code",
|
"name": "stderr",
|
||||||
"ordinal": 9,
|
"ordinal": 9,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "exit_code",
|
||||||
|
"ordinal": 10,
|
||||||
"type_info": "Integer"
|
"type_info": "Integer"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "started_at!: DateTime<Utc>",
|
"name": "started_at!: DateTime<Utc>",
|
||||||
"ordinal": 10,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "completed_at?: DateTime<Utc>",
|
|
||||||
"ordinal": 11,
|
"ordinal": 11,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "created_at!: DateTime<Utc>",
|
"name": "completed_at?: DateTime<Utc>",
|
||||||
"ordinal": 12,
|
"ordinal": 12,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "updated_at!: DateTime<Utc>",
|
"name": "created_at!: DateTime<Utc>",
|
||||||
"ordinal": 13,
|
"ordinal": 13,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 14,
|
||||||
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -81,6 +86,7 @@
|
|||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
@@ -94,5 +100,5 @@
|
|||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "1ada5613889792cd6098da71ce2ba1ecdee7e5dc2ff8196872368fff0caa48d8"
|
"hash": "9472c8fb477958167f5fae40b85ac44252468c5226b2cdd7770f027332eed6d7"
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "SELECT \n id as \"id!: Uuid\", \n task_attempt_id as \"task_attempt_id!: Uuid\", \n process_type as \"process_type!: ExecutionProcessType\",\n status as \"status!: ExecutionProcessStatus\",\n command, \n args, \n working_directory, \n stdout, \n stderr, \n exit_code,\n started_at as \"started_at!: DateTime<Utc>\",\n completed_at as \"completed_at?: DateTime<Utc>\",\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM execution_processes \n WHERE task_attempt_id = $1 \n ORDER BY created_at ASC",
|
"query": "SELECT \n id as \"id!: Uuid\", \n task_attempt_id as \"task_attempt_id!: Uuid\", \n process_type as \"process_type!: ExecutionProcessType\",\n executor_type,\n status as \"status!: ExecutionProcessStatus\",\n command, \n args, \n working_directory, \n stdout, \n stderr, \n exit_code,\n started_at as \"started_at!: DateTime<Utc>\",\n completed_at as \"completed_at?: DateTime<Utc>\",\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM execution_processes \n WHERE id = $1",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -19,59 +19,64 @@
|
|||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "status!: ExecutionProcessStatus",
|
"name": "executor_type",
|
||||||
"ordinal": 3,
|
"ordinal": 3,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "command",
|
"name": "status!: ExecutionProcessStatus",
|
||||||
"ordinal": 4,
|
"ordinal": 4,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "args",
|
"name": "command",
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "working_directory",
|
"name": "args",
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "stdout",
|
"name": "working_directory",
|
||||||
"ordinal": 7,
|
"ordinal": 7,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "stderr",
|
"name": "stdout",
|
||||||
"ordinal": 8,
|
"ordinal": 8,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "exit_code",
|
"name": "stderr",
|
||||||
"ordinal": 9,
|
"ordinal": 9,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "exit_code",
|
||||||
|
"ordinal": 10,
|
||||||
"type_info": "Integer"
|
"type_info": "Integer"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "started_at!: DateTime<Utc>",
|
"name": "started_at!: DateTime<Utc>",
|
||||||
"ordinal": 10,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "completed_at?: DateTime<Utc>",
|
|
||||||
"ordinal": 11,
|
"ordinal": 11,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "created_at!: DateTime<Utc>",
|
"name": "completed_at?: DateTime<Utc>",
|
||||||
"ordinal": 12,
|
"ordinal": 12,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "updated_at!: DateTime<Utc>",
|
"name": "created_at!: DateTime<Utc>",
|
||||||
"ordinal": 13,
|
"ordinal": 13,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 14,
|
||||||
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -81,6 +86,7 @@
|
|||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
@@ -94,5 +100,5 @@
|
|||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "14ad9267623a3aa678a943db6d0b14581a6d353da673c9e4156e0bbeac0b3346"
|
"hash": "9edb2c01e91fd0f0fe7b56e988c7ae0393150f50be3f419a981e035c0121dfc7"
|
||||||
}
|
}
|
||||||
56
backend/.sqlx/query-a528a9926fab1c819a5a1fa1cde87ea9d354da0873af22e888d0bf8e0c7f306a.json
generated
Normal file
56
backend/.sqlx/query-a528a9926fab1c819a5a1fa1cde87ea9d354da0873af22e888d0bf8e0c7f306a.json
generated
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "INSERT INTO executor_sessions (\n id, task_attempt_id, execution_process_id, session_id, prompt, \n created_at, updated_at\n ) \n VALUES ($1, $2, $3, $4, $5, $6, $7) \n RETURNING \n id as \"id!: Uuid\", \n task_attempt_id as \"task_attempt_id!: Uuid\", \n execution_process_id as \"execution_process_id!: Uuid\", \n session_id, \n prompt,\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id!: Uuid",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Blob"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "task_attempt_id!: Uuid",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Blob"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "execution_process_id!: Uuid",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Blob"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "session_id",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "prompt",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 7
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "a528a9926fab1c819a5a1fa1cde87ea9d354da0873af22e888d0bf8e0c7f306a"
|
||||||
|
}
|
||||||
12
backend/.sqlx/query-d3b9ea1de1576af71b312924ce7f4ea8ae5dbe2ac138ea3b4470f2d5cd734846.json
generated
Normal file
12
backend/.sqlx/query-d3b9ea1de1576af71b312924ce7f4ea8ae5dbe2ac138ea3b4470f2d5cd734846.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "UPDATE executor_sessions \n SET prompt = $1, updated_at = datetime('now') \n WHERE id = $2",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "d3b9ea1de1576af71b312924ce7f4ea8ae5dbe2ac138ea3b4470f2d5cd734846"
|
||||||
|
}
|
||||||
17
backend/migrations/20250623120000_executor_sessions.sql
Normal file
17
backend/migrations/20250623120000_executor_sessions.sql
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
PRAGMA foreign_keys = ON;
|
||||||
|
|
||||||
|
CREATE TABLE executor_sessions (
|
||||||
|
id BLOB PRIMARY KEY,
|
||||||
|
task_attempt_id BLOB NOT NULL,
|
||||||
|
execution_process_id BLOB NOT NULL,
|
||||||
|
session_id TEXT, -- External session ID from Claude/Amp
|
||||||
|
prompt TEXT, -- The prompt sent to the executor
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),
|
||||||
|
updated_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),
|
||||||
|
FOREIGN KEY (task_attempt_id) REFERENCES task_attempts(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (execution_process_id) REFERENCES execution_processes(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_executor_sessions_task_attempt_id ON executor_sessions(task_attempt_id);
|
||||||
|
CREATE INDEX idx_executor_sessions_execution_process_id ON executor_sessions(execution_process_id);
|
||||||
|
CREATE INDEX idx_executor_sessions_session_id ON executor_sessions(session_id);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
PRAGMA foreign_keys = ON;
|
||||||
|
|
||||||
|
-- Add executor_type column to execution_processes table
|
||||||
|
ALTER TABLE execution_processes ADD COLUMN executor_type TEXT;
|
||||||
@@ -1,6 +1,40 @@
|
|||||||
use std::{env, fs, path::Path};
|
use std::{env, fs, path::Path};
|
||||||
use ts_rs::TS; // in [build-dependencies]
|
use ts_rs::TS; // in [build-dependencies]
|
||||||
|
|
||||||
|
fn generate_constants() -> String {
|
||||||
|
r#"// Generated constants
|
||||||
|
export const EXECUTOR_TYPES: string[] = [
|
||||||
|
"echo",
|
||||||
|
"claude",
|
||||||
|
"amp"
|
||||||
|
];
|
||||||
|
|
||||||
|
export const EDITOR_TYPES: EditorType[] = [
|
||||||
|
"vscode",
|
||||||
|
"cursor",
|
||||||
|
"windsurf",
|
||||||
|
"intellij",
|
||||||
|
"zed",
|
||||||
|
"custom"
|
||||||
|
];
|
||||||
|
|
||||||
|
export const EXECUTOR_LABELS: Record<string, string> = {
|
||||||
|
"echo": "Echo (Test Mode)",
|
||||||
|
"claude": "Claude",
|
||||||
|
"amp": "Amp"
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EDITOR_LABELS: Record<string, string> = {
|
||||||
|
"vscode": "VS Code",
|
||||||
|
"cursor": "Cursor",
|
||||||
|
"windsurf": "Windsurf",
|
||||||
|
"intellij": "IntelliJ IDEA",
|
||||||
|
"zed": "Zed",
|
||||||
|
"custom": "Custom"
|
||||||
|
};"#
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// 1. Make sure ../shared exists
|
// 1. Make sure ../shared exists
|
||||||
let shared_path = Path::new("../shared");
|
let shared_path = Path::new("../shared");
|
||||||
@@ -18,7 +52,9 @@ fn main() {
|
|||||||
vibe_kanban::models::config::ThemeMode::decl(),
|
vibe_kanban::models::config::ThemeMode::decl(),
|
||||||
vibe_kanban::models::config::EditorConfig::decl(),
|
vibe_kanban::models::config::EditorConfig::decl(),
|
||||||
vibe_kanban::models::config::EditorType::decl(),
|
vibe_kanban::models::config::EditorType::decl(),
|
||||||
|
vibe_kanban::models::config::EditorConstants::decl(),
|
||||||
vibe_kanban::executor::ExecutorConfig::decl(),
|
vibe_kanban::executor::ExecutorConfig::decl(),
|
||||||
|
vibe_kanban::executor::ExecutorConstants::decl(),
|
||||||
vibe_kanban::models::project::CreateProject::decl(),
|
vibe_kanban::models::project::CreateProject::decl(),
|
||||||
vibe_kanban::models::project::Project::decl(),
|
vibe_kanban::models::project::Project::decl(),
|
||||||
vibe_kanban::models::project::UpdateProject::decl(),
|
vibe_kanban::models::project::UpdateProject::decl(),
|
||||||
@@ -34,6 +70,7 @@ fn main() {
|
|||||||
vibe_kanban::models::task_attempt::TaskAttempt::decl(),
|
vibe_kanban::models::task_attempt::TaskAttempt::decl(),
|
||||||
vibe_kanban::models::task_attempt::CreateTaskAttempt::decl(),
|
vibe_kanban::models::task_attempt::CreateTaskAttempt::decl(),
|
||||||
vibe_kanban::models::task_attempt::UpdateTaskAttempt::decl(),
|
vibe_kanban::models::task_attempt::UpdateTaskAttempt::decl(),
|
||||||
|
vibe_kanban::models::task_attempt::CreateFollowUpAttempt::decl(),
|
||||||
vibe_kanban::models::task_attempt_activity::TaskAttemptActivity::decl(),
|
vibe_kanban::models::task_attempt_activity::TaskAttemptActivity::decl(),
|
||||||
vibe_kanban::models::task_attempt_activity::CreateTaskAttemptActivity::decl(),
|
vibe_kanban::models::task_attempt_activity::CreateTaskAttemptActivity::decl(),
|
||||||
vibe_kanban::routes::filesystem::DirectoryEntry::decl(),
|
vibe_kanban::routes::filesystem::DirectoryEntry::decl(),
|
||||||
@@ -47,6 +84,9 @@ fn main() {
|
|||||||
vibe_kanban::models::execution_process::ExecutionProcessType::decl(),
|
vibe_kanban::models::execution_process::ExecutionProcessType::decl(),
|
||||||
vibe_kanban::models::execution_process::CreateExecutionProcess::decl(),
|
vibe_kanban::models::execution_process::CreateExecutionProcess::decl(),
|
||||||
vibe_kanban::models::execution_process::UpdateExecutionProcess::decl(),
|
vibe_kanban::models::execution_process::UpdateExecutionProcess::decl(),
|
||||||
|
vibe_kanban::models::executor_session::ExecutorSession::decl(),
|
||||||
|
vibe_kanban::models::executor_session::CreateExecutorSession::decl(),
|
||||||
|
vibe_kanban::models::executor_session::UpdateExecutorSession::decl(),
|
||||||
];
|
];
|
||||||
|
|
||||||
// 4. Friendly banner
|
// 4. Friendly banner
|
||||||
@@ -69,9 +109,15 @@ fn main() {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n\n");
|
.join("\n\n");
|
||||||
|
|
||||||
// 6. Write the consolidated types.ts
|
// 6. Add constants
|
||||||
fs::write(shared_path.join("types.ts"), format!("{HEADER}{body}"))
|
let constants = generate_constants();
|
||||||
.expect("unable to write types.ts");
|
|
||||||
|
// 7. Write the consolidated types.ts
|
||||||
|
fs::write(
|
||||||
|
shared_path.join("types.ts"),
|
||||||
|
format!("{HEADER}{body}\n\n{constants}"),
|
||||||
|
)
|
||||||
|
.expect("unable to write types.ts");
|
||||||
|
|
||||||
println!("✅ TypeScript types generated in ../shared/");
|
println!("✅ TypeScript types generated in ../shared/");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,11 @@ pub trait Executor: Send + Sync {
|
|||||||
pub enum ExecutorType {
|
pub enum ExecutorType {
|
||||||
SetupScript(String),
|
SetupScript(String),
|
||||||
CodingAgent(ExecutorConfig),
|
CodingAgent(ExecutorConfig),
|
||||||
|
FollowUpCodingAgent {
|
||||||
|
config: ExecutorConfig,
|
||||||
|
session_id: Option<String>,
|
||||||
|
prompt: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration for different executor types
|
/// Configuration for different executor types
|
||||||
@@ -107,6 +112,31 @@ pub enum ExecutorConfig {
|
|||||||
// Docker { image: String, command: String },
|
// Docker { image: String, command: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constants for frontend
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct ExecutorConstants {
|
||||||
|
pub executor_types: Vec<ExecutorConfig>,
|
||||||
|
pub executor_labels: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecutorConstants {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
executor_types: vec![
|
||||||
|
ExecutorConfig::Echo,
|
||||||
|
ExecutorConfig::Claude,
|
||||||
|
ExecutorConfig::Amp,
|
||||||
|
],
|
||||||
|
executor_labels: vec![
|
||||||
|
"Echo (Test Mode)".to_string(),
|
||||||
|
"Claude".to_string(),
|
||||||
|
"Amp".to_string(),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ExecutorConfig {
|
impl ExecutorConfig {
|
||||||
pub fn create_executor(&self) -> Box<dyn Executor> {
|
pub fn create_executor(&self) -> Box<dyn Executor> {
|
||||||
match self {
|
match self {
|
||||||
@@ -126,17 +156,45 @@ pub async fn stream_output_to_db(
|
|||||||
is_stdout: bool,
|
is_stdout: bool,
|
||||||
) {
|
) {
|
||||||
use crate::models::execution_process::ExecutionProcess;
|
use crate::models::execution_process::ExecutionProcess;
|
||||||
|
use crate::models::executor_session::ExecutorSession;
|
||||||
|
|
||||||
let mut reader = BufReader::new(output);
|
let mut reader = BufReader::new(output);
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
let mut accumulated_output = String::new();
|
let mut accumulated_output = String::new();
|
||||||
let mut update_counter = 0;
|
let mut update_counter = 0;
|
||||||
|
let mut session_id_parsed = false;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
line.clear();
|
line.clear();
|
||||||
match reader.read_line(&mut line).await {
|
match reader.read_line(&mut line).await {
|
||||||
Ok(0) => break, // EOF
|
Ok(0) => break, // EOF
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
|
// Parse session ID from the first JSONL line (stdout only)
|
||||||
|
if is_stdout && !session_id_parsed {
|
||||||
|
if let Some(external_session_id) = parse_session_id_from_line(&line) {
|
||||||
|
if let Err(e) = ExecutorSession::update_session_id(
|
||||||
|
&pool,
|
||||||
|
execution_process_id,
|
||||||
|
&external_session_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::error!(
|
||||||
|
"Failed to update session ID for execution process {}: {}",
|
||||||
|
execution_process_id,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
tracing::info!(
|
||||||
|
"Updated session ID {} for execution process {}",
|
||||||
|
external_session_id,
|
||||||
|
execution_process_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
session_id_parsed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
accumulated_output.push_str(&line);
|
accumulated_output.push_str(&line);
|
||||||
update_counter += 1;
|
update_counter += 1;
|
||||||
|
|
||||||
@@ -208,3 +266,71 @@ pub async fn stream_output_to_db(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse session_id from Claude or thread_id from Amp from the first JSONL line
|
||||||
|
fn parse_session_id_from_line(line: &str) -> Option<String> {
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
let trimmed = line.trim();
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to parse as JSON
|
||||||
|
if let Ok(json) = serde_json::from_str::<Value>(trimmed) {
|
||||||
|
// Check for Claude session_id
|
||||||
|
if let Some(session_id) = json.get("session_id").and_then(|v| v.as_str()) {
|
||||||
|
return Some(session_id.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Amp threadID
|
||||||
|
if let Some(thread_id) = json.get("threadID").and_then(|v| v.as_str()) {
|
||||||
|
return Some(thread_id.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_claude_session_id() {
|
||||||
|
let claude_line = r#"{"type":"system","subtype":"init","cwd":"/private/tmp/mission-control-worktree-3abb979d-2e0e-4404-a276-c16d98a97dd5","session_id":"cc0889a2-0c59-43cc-926b-739a983888a2","tools":["Task","Bash","Glob","Grep","LS","exit_plan_mode","Read","Edit","MultiEdit","Write","NotebookRead","NotebookEdit","WebFetch","TodoRead","TodoWrite","WebSearch"],"mcp_servers":[],"model":"claude-sonnet-4-20250514","permissionMode":"bypassPermissions","apiKeySource":"/login managed key"}"#;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse_session_id_from_line(claude_line),
|
||||||
|
Some("cc0889a2-0c59-43cc-926b-739a983888a2".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_amp_thread_id() {
|
||||||
|
let amp_line = r#"{"type":"initial","threadID":"T-286f908a-2cd8-40cc-9490-da689b2f1560"}"#;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse_session_id_from_line(amp_line),
|
||||||
|
Some("T-286f908a-2cd8-40cc-9490-da689b2f1560".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_invalid_json() {
|
||||||
|
let invalid_line = "not json at all";
|
||||||
|
assert_eq!(parse_session_id_from_line(invalid_line), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_json_without_ids() {
|
||||||
|
let other_json = r#"{"type":"other","message":"hello"}"#;
|
||||||
|
assert_eq!(parse_session_id_from_line(other_json), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_empty_line() {
|
||||||
|
assert_eq!(parse_session_id_from_line(""), None);
|
||||||
|
assert_eq!(parse_session_id_from_line(" "), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,9 +5,15 @@ use uuid::Uuid;
|
|||||||
use crate::executor::{Executor, ExecutorError};
|
use crate::executor::{Executor, ExecutorError};
|
||||||
use crate::models::task::Task;
|
use crate::models::task::Task;
|
||||||
|
|
||||||
/// An executor that uses Claude CLI to process tasks
|
/// An executor that uses Amp to process tasks
|
||||||
pub struct AmpExecutor;
|
pub struct AmpExecutor;
|
||||||
|
|
||||||
|
/// An executor that continues an Amp thread
|
||||||
|
pub struct AmpFollowupExecutor {
|
||||||
|
pub thread_id: String,
|
||||||
|
pub prompt: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Executor for AmpExecutor {
|
impl Executor for AmpExecutor {
|
||||||
async fn spawn(
|
async fn spawn(
|
||||||
@@ -53,3 +59,39 @@ impl Executor for AmpExecutor {
|
|||||||
Ok(child)
|
Ok(child)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Executor for AmpFollowupExecutor {
|
||||||
|
async fn spawn(
|
||||||
|
&self,
|
||||||
|
pool: &sqlx::SqlitePool,
|
||||||
|
task_id: Uuid,
|
||||||
|
worktree_path: &str,
|
||||||
|
) -> Result<Child, ExecutorError> {
|
||||||
|
use std::process::Stdio;
|
||||||
|
use tokio::{io::AsyncWriteExt, process::Command};
|
||||||
|
|
||||||
|
let mut child = Command::new("npx")
|
||||||
|
.kill_on_drop(true)
|
||||||
|
.stdin(Stdio::piped()) // <-- open a pipe
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.current_dir(worktree_path)
|
||||||
|
.arg("@sourcegraph/amp")
|
||||||
|
.arg("threads")
|
||||||
|
.arg("continue")
|
||||||
|
.arg(&self.thread_id)
|
||||||
|
.arg("--format=jsonl")
|
||||||
|
.process_group(0) // Create new process group so we can kill entire tree
|
||||||
|
.spawn()
|
||||||
|
.map_err(ExecutorError::SpawnFailed)?;
|
||||||
|
|
||||||
|
// feed the prompt in, then close the pipe so `amp` sees EOF
|
||||||
|
if let Some(mut stdin) = child.stdin.take() {
|
||||||
|
stdin.write_all(self.prompt.as_bytes()).await.unwrap();
|
||||||
|
stdin.shutdown().await.unwrap(); // or `drop(stdin);`
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ use crate::models::task::Task;
|
|||||||
/// An executor that uses Claude CLI to process tasks
|
/// An executor that uses Claude CLI to process tasks
|
||||||
pub struct ClaudeExecutor;
|
pub struct ClaudeExecutor;
|
||||||
|
|
||||||
|
/// An executor that resumes a Claude session
|
||||||
|
pub struct ClaudeFollowupExecutor {
|
||||||
|
pub session_id: String,
|
||||||
|
pub prompt: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Executor for ClaudeExecutor {
|
impl Executor for ClaudeExecutor {
|
||||||
async fn spawn(
|
async fn spawn(
|
||||||
@@ -49,3 +55,32 @@ impl Executor for ClaudeExecutor {
|
|||||||
Ok(child)
|
Ok(child)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Executor for ClaudeFollowupExecutor {
|
||||||
|
async fn spawn(
|
||||||
|
&self,
|
||||||
|
pool: &sqlx::SqlitePool,
|
||||||
|
task_id: Uuid,
|
||||||
|
worktree_path: &str,
|
||||||
|
) -> Result<Child, ExecutorError> {
|
||||||
|
// Use Claude CLI with --resume flag to continue the session
|
||||||
|
let child = Command::new("claude")
|
||||||
|
.kill_on_drop(true)
|
||||||
|
.stdin(std::process::Stdio::null())
|
||||||
|
.stdout(std::process::Stdio::piped())
|
||||||
|
.stderr(std::process::Stdio::piped())
|
||||||
|
.current_dir(worktree_path)
|
||||||
|
.arg(&self.prompt)
|
||||||
|
.arg("-p")
|
||||||
|
.arg("--dangerously-skip-permissions")
|
||||||
|
.arg("--verbose")
|
||||||
|
.arg("--output-format=stream-json")
|
||||||
|
.arg(format!("--resume={}", self.session_id))
|
||||||
|
.process_group(0) // Create new process group so we can kill entire tree
|
||||||
|
.spawn()
|
||||||
|
.map_err(ExecutorError::SpawnFailed)?;
|
||||||
|
|
||||||
|
Ok(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ pub mod claude;
|
|||||||
pub mod echo;
|
pub mod echo;
|
||||||
pub mod setup_script;
|
pub mod setup_script;
|
||||||
|
|
||||||
pub use amp::AmpExecutor;
|
pub use amp::{AmpExecutor, AmpFollowupExecutor};
|
||||||
pub use claude::ClaudeExecutor;
|
pub use claude::{ClaudeExecutor, ClaudeFollowupExecutor};
|
||||||
pub use echo::EchoExecutor;
|
pub use echo::EchoExecutor;
|
||||||
pub use setup_script::SetupScriptExecutor;
|
pub use setup_script::SetupScriptExecutor;
|
||||||
|
|||||||
@@ -43,6 +43,37 @@ pub enum EditorType {
|
|||||||
Custom,
|
Custom,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constants for frontend
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct EditorConstants {
|
||||||
|
pub editor_types: Vec<EditorType>,
|
||||||
|
pub editor_labels: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EditorConstants {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
editor_types: vec![
|
||||||
|
EditorType::VSCode,
|
||||||
|
EditorType::Cursor,
|
||||||
|
EditorType::Windsurf,
|
||||||
|
EditorType::IntelliJ,
|
||||||
|
EditorType::Zed,
|
||||||
|
EditorType::Custom,
|
||||||
|
],
|
||||||
|
editor_labels: vec![
|
||||||
|
"VS Code".to_string(),
|
||||||
|
"Cursor".to_string(),
|
||||||
|
"Windsurf".to_string(),
|
||||||
|
"IntelliJ IDEA".to_string(),
|
||||||
|
"Zed".to_string(),
|
||||||
|
"Custom".to_string(),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ pub struct ExecutionProcess {
|
|||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub task_attempt_id: Uuid,
|
pub task_attempt_id: Uuid,
|
||||||
pub process_type: ExecutionProcessType,
|
pub process_type: ExecutionProcessType,
|
||||||
|
pub executor_type: Option<String>, // "echo", "claude", "amp", etc. - only for CodingAgent processes
|
||||||
pub status: ExecutionProcessStatus,
|
pub status: ExecutionProcessStatus,
|
||||||
pub command: String,
|
pub command: String,
|
||||||
pub args: Option<String>, // JSON array of arguments
|
pub args: Option<String>, // JSON array of arguments
|
||||||
@@ -71,6 +72,7 @@ pub struct ExecutionProcess {
|
|||||||
pub struct CreateExecutionProcess {
|
pub struct CreateExecutionProcess {
|
||||||
pub task_attempt_id: Uuid,
|
pub task_attempt_id: Uuid,
|
||||||
pub process_type: ExecutionProcessType,
|
pub process_type: ExecutionProcessType,
|
||||||
|
pub executor_type: Option<String>,
|
||||||
pub command: String,
|
pub command: String,
|
||||||
pub args: Option<String>,
|
pub args: Option<String>,
|
||||||
pub working_directory: String,
|
pub working_directory: String,
|
||||||
@@ -93,6 +95,7 @@ impl ExecutionProcess {
|
|||||||
id as "id!: Uuid",
|
id as "id!: Uuid",
|
||||||
task_attempt_id as "task_attempt_id!: Uuid",
|
task_attempt_id as "task_attempt_id!: Uuid",
|
||||||
process_type as "process_type!: ExecutionProcessType",
|
process_type as "process_type!: ExecutionProcessType",
|
||||||
|
executor_type,
|
||||||
status as "status!: ExecutionProcessStatus",
|
status as "status!: ExecutionProcessStatus",
|
||||||
command,
|
command,
|
||||||
args,
|
args,
|
||||||
@@ -123,6 +126,7 @@ impl ExecutionProcess {
|
|||||||
id as "id!: Uuid",
|
id as "id!: Uuid",
|
||||||
task_attempt_id as "task_attempt_id!: Uuid",
|
task_attempt_id as "task_attempt_id!: Uuid",
|
||||||
process_type as "process_type!: ExecutionProcessType",
|
process_type as "process_type!: ExecutionProcessType",
|
||||||
|
executor_type,
|
||||||
status as "status!: ExecutionProcessStatus",
|
status as "status!: ExecutionProcessStatus",
|
||||||
command,
|
command,
|
||||||
args,
|
args,
|
||||||
@@ -151,6 +155,7 @@ impl ExecutionProcess {
|
|||||||
id as "id!: Uuid",
|
id as "id!: Uuid",
|
||||||
task_attempt_id as "task_attempt_id!: Uuid",
|
task_attempt_id as "task_attempt_id!: Uuid",
|
||||||
process_type as "process_type!: ExecutionProcessType",
|
process_type as "process_type!: ExecutionProcessType",
|
||||||
|
executor_type,
|
||||||
status as "status!: ExecutionProcessStatus",
|
status as "status!: ExecutionProcessStatus",
|
||||||
command,
|
command,
|
||||||
args,
|
args,
|
||||||
@@ -181,15 +186,16 @@ impl ExecutionProcess {
|
|||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
ExecutionProcess,
|
ExecutionProcess,
|
||||||
r#"INSERT INTO execution_processes (
|
r#"INSERT INTO execution_processes (
|
||||||
id, task_attempt_id, process_type, status, command, args,
|
id, task_attempt_id, process_type, executor_type, status, command, args,
|
||||||
working_directory, stdout, stderr, exit_code, started_at,
|
working_directory, stdout, stderr, exit_code, started_at,
|
||||||
completed_at, created_at, updated_at
|
completed_at, created_at, updated_at
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
|
||||||
RETURNING
|
RETURNING
|
||||||
id as "id!: Uuid",
|
id as "id!: Uuid",
|
||||||
task_attempt_id as "task_attempt_id!: Uuid",
|
task_attempt_id as "task_attempt_id!: Uuid",
|
||||||
process_type as "process_type!: ExecutionProcessType",
|
process_type as "process_type!: ExecutionProcessType",
|
||||||
|
executor_type,
|
||||||
status as "status!: ExecutionProcessStatus",
|
status as "status!: ExecutionProcessStatus",
|
||||||
command,
|
command,
|
||||||
args,
|
args,
|
||||||
@@ -204,6 +210,7 @@ impl ExecutionProcess {
|
|||||||
process_id,
|
process_id,
|
||||||
data.task_attempt_id,
|
data.task_attempt_id,
|
||||||
data.process_type,
|
data.process_type,
|
||||||
|
data.executor_type,
|
||||||
ExecutionProcessStatus::Running,
|
ExecutionProcessStatus::Running,
|
||||||
data.command,
|
data.command,
|
||||||
data.args,
|
data.args,
|
||||||
|
|||||||
189
backend/src/models/executor_session.rs
Normal file
189
backend/src/models/executor_session.rs
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::{FromRow, SqlitePool};
|
||||||
|
use ts_rs::TS;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct ExecutorSession {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub task_attempt_id: Uuid,
|
||||||
|
pub execution_process_id: Uuid,
|
||||||
|
pub session_id: Option<String>, // External session ID from Claude/Amp
|
||||||
|
pub prompt: Option<String>, // The prompt sent to the executor
|
||||||
|
pub created_at: DateTime<Utc>,
|
||||||
|
pub updated_at: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct CreateExecutorSession {
|
||||||
|
pub task_attempt_id: Uuid,
|
||||||
|
pub execution_process_id: Uuid,
|
||||||
|
pub prompt: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct UpdateExecutorSession {
|
||||||
|
pub session_id: Option<String>,
|
||||||
|
pub prompt: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecutorSession {
|
||||||
|
/// Find executor session by ID
|
||||||
|
pub async fn find_by_id(pool: &SqlitePool, id: Uuid) -> Result<Option<Self>, sqlx::Error> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
ExecutorSession,
|
||||||
|
r#"SELECT
|
||||||
|
id as "id!: Uuid",
|
||||||
|
task_attempt_id as "task_attempt_id!: Uuid",
|
||||||
|
execution_process_id as "execution_process_id!: Uuid",
|
||||||
|
session_id,
|
||||||
|
prompt,
|
||||||
|
created_at as "created_at!: DateTime<Utc>",
|
||||||
|
updated_at as "updated_at!: DateTime<Utc>"
|
||||||
|
FROM executor_sessions
|
||||||
|
WHERE id = $1"#,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.fetch_optional(pool)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find executor session by execution process ID
|
||||||
|
pub async fn find_by_execution_process_id(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
execution_process_id: Uuid,
|
||||||
|
) -> Result<Option<Self>, sqlx::Error> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
ExecutorSession,
|
||||||
|
r#"SELECT
|
||||||
|
id as "id!: Uuid",
|
||||||
|
task_attempt_id as "task_attempt_id!: Uuid",
|
||||||
|
execution_process_id as "execution_process_id!: Uuid",
|
||||||
|
session_id,
|
||||||
|
prompt,
|
||||||
|
created_at as "created_at!: DateTime<Utc>",
|
||||||
|
updated_at as "updated_at!: DateTime<Utc>"
|
||||||
|
FROM executor_sessions
|
||||||
|
WHERE execution_process_id = $1"#,
|
||||||
|
execution_process_id
|
||||||
|
)
|
||||||
|
.fetch_optional(pool)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find all executor sessions for a task attempt
|
||||||
|
pub async fn find_by_task_attempt_id(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
task_attempt_id: Uuid,
|
||||||
|
) -> Result<Vec<Self>, sqlx::Error> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
ExecutorSession,
|
||||||
|
r#"SELECT
|
||||||
|
id as "id!: Uuid",
|
||||||
|
task_attempt_id as "task_attempt_id!: Uuid",
|
||||||
|
execution_process_id as "execution_process_id!: Uuid",
|
||||||
|
session_id,
|
||||||
|
prompt,
|
||||||
|
created_at as "created_at!: DateTime<Utc>",
|
||||||
|
updated_at as "updated_at!: DateTime<Utc>"
|
||||||
|
FROM executor_sessions
|
||||||
|
WHERE task_attempt_id = $1
|
||||||
|
ORDER BY created_at ASC"#,
|
||||||
|
task_attempt_id
|
||||||
|
)
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new executor session
|
||||||
|
pub async fn create(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
data: &CreateExecutorSession,
|
||||||
|
session_id: Uuid,
|
||||||
|
) -> Result<Self, sqlx::Error> {
|
||||||
|
let now = Utc::now();
|
||||||
|
|
||||||
|
sqlx::query_as!(
|
||||||
|
ExecutorSession,
|
||||||
|
r#"INSERT INTO executor_sessions (
|
||||||
|
id, task_attempt_id, execution_process_id, session_id, prompt,
|
||||||
|
created_at, updated_at
|
||||||
|
)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||||
|
RETURNING
|
||||||
|
id as "id!: Uuid",
|
||||||
|
task_attempt_id as "task_attempt_id!: Uuid",
|
||||||
|
execution_process_id as "execution_process_id!: Uuid",
|
||||||
|
session_id,
|
||||||
|
prompt,
|
||||||
|
created_at as "created_at!: DateTime<Utc>",
|
||||||
|
updated_at as "updated_at!: DateTime<Utc>""#,
|
||||||
|
session_id,
|
||||||
|
data.task_attempt_id,
|
||||||
|
data.execution_process_id,
|
||||||
|
None::<String>, // session_id initially None until parsed from output
|
||||||
|
data.prompt,
|
||||||
|
now, // created_at
|
||||||
|
now // updated_at
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update executor session with external session ID
|
||||||
|
pub async fn update_session_id(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
execution_process_id: Uuid,
|
||||||
|
external_session_id: &str,
|
||||||
|
) -> Result<(), sqlx::Error> {
|
||||||
|
sqlx::query!(
|
||||||
|
r#"UPDATE executor_sessions
|
||||||
|
SET session_id = $1, updated_at = datetime('now')
|
||||||
|
WHERE execution_process_id = $2"#,
|
||||||
|
external_session_id,
|
||||||
|
execution_process_id
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update executor session prompt
|
||||||
|
pub async fn update_prompt(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
id: Uuid,
|
||||||
|
prompt: &str,
|
||||||
|
) -> Result<(), sqlx::Error> {
|
||||||
|
sqlx::query!(
|
||||||
|
r#"UPDATE executor_sessions
|
||||||
|
SET prompt = $1, updated_at = datetime('now')
|
||||||
|
WHERE id = $2"#,
|
||||||
|
prompt,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete executor sessions for a task attempt (cleanup)
|
||||||
|
pub async fn delete_by_task_attempt_id(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
task_attempt_id: Uuid,
|
||||||
|
) -> Result<(), sqlx::Error> {
|
||||||
|
sqlx::query!(
|
||||||
|
"DELETE FROM executor_sessions WHERE task_attempt_id = $1",
|
||||||
|
task_attempt_id
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
pub mod api_response;
|
pub mod api_response;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod execution_process;
|
pub mod execution_process;
|
||||||
|
pub mod executor_session;
|
||||||
pub mod project;
|
pub mod project;
|
||||||
pub mod task;
|
pub mod task;
|
||||||
pub mod task_attempt;
|
pub mod task_attempt;
|
||||||
|
|||||||
@@ -82,6 +82,12 @@ pub struct UpdateTaskAttempt {
|
|||||||
// Currently no updateable fields, but keeping struct for API compatibility
|
// Currently no updateable fields, but keeping struct for API compatibility
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct CreateFollowUpAttempt {
|
||||||
|
pub prompt: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub enum DiffChunkType {
|
pub enum DiffChunkType {
|
||||||
@@ -376,10 +382,37 @@ impl TaskAttempt {
|
|||||||
task_id: Uuid,
|
task_id: Uuid,
|
||||||
project_id: Uuid,
|
project_id: Uuid,
|
||||||
) -> Result<(), TaskAttemptError> {
|
) -> Result<(), TaskAttemptError> {
|
||||||
use crate::models::project::Project;
|
|
||||||
use crate::models::task::{Task, TaskStatus};
|
use crate::models::task::{Task, TaskStatus};
|
||||||
|
|
||||||
// Get the task attempt, task, and project
|
// Load required entities
|
||||||
|
let (task_attempt, project) =
|
||||||
|
Self::load_execution_context(pool, attempt_id, project_id).await?;
|
||||||
|
|
||||||
|
// Update task status to indicate execution has started
|
||||||
|
Task::update_status(pool, task_id, project_id, TaskStatus::InProgress).await?;
|
||||||
|
|
||||||
|
// Determine execution sequence based on project configuration
|
||||||
|
if Self::should_run_setup_script(&project) {
|
||||||
|
Self::start_setup_script(
|
||||||
|
pool,
|
||||||
|
app_state,
|
||||||
|
attempt_id,
|
||||||
|
task_id,
|
||||||
|
&project,
|
||||||
|
&task_attempt.worktree_path,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
Self::start_coding_agent(pool, app_state, attempt_id, task_id, project_id).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load the execution context (task attempt and project) with validation
|
||||||
|
async fn load_execution_context(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
attempt_id: Uuid,
|
||||||
|
project_id: Uuid,
|
||||||
|
) -> Result<(TaskAttempt, Project), TaskAttemptError> {
|
||||||
let task_attempt = TaskAttempt::find_by_id(pool, attempt_id)
|
let task_attempt = TaskAttempt::find_by_id(pool, attempt_id)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(TaskAttemptError::TaskNotFound)?;
|
.ok_or(TaskAttemptError::TaskNotFound)?;
|
||||||
@@ -388,36 +421,149 @@ impl TaskAttempt {
|
|||||||
.await?
|
.await?
|
||||||
.ok_or(TaskAttemptError::ProjectNotFound)?;
|
.ok_or(TaskAttemptError::ProjectNotFound)?;
|
||||||
|
|
||||||
// Update task status to InProgress at the start of execution (during setup)
|
Ok((task_attempt, project))
|
||||||
Task::update_status(pool, task_id, project_id, TaskStatus::InProgress).await?;
|
|
||||||
|
|
||||||
// Step 1: Run setup script if it exists
|
|
||||||
if let Some(setup_script) = &project.setup_script {
|
|
||||||
if !setup_script.trim().is_empty() {
|
|
||||||
Self::start_process_execution(
|
|
||||||
pool,
|
|
||||||
app_state,
|
|
||||||
attempt_id,
|
|
||||||
task_id,
|
|
||||||
crate::executor::ExecutorType::SetupScript(setup_script.clone()),
|
|
||||||
"Starting setup script".to_string(),
|
|
||||||
TaskAttemptStatus::SetupRunning,
|
|
||||||
crate::models::execution_process::ExecutionProcessType::SetupScript,
|
|
||||||
&task_attempt.worktree_path,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Wait for setup script to complete before starting executor
|
|
||||||
// We'll let the execution monitor handle the completion and then start the executor
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no setup script, start executor directly
|
|
||||||
Self::start_coding_agent(pool, app_state, attempt_id, task_id, project_id).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unified function to start any type of process execution (setup script or coding agent)
|
/// Check if setup script should be executed
|
||||||
|
fn should_run_setup_script(project: &Project) -> bool {
|
||||||
|
project
|
||||||
|
.setup_script
|
||||||
|
.as_ref()
|
||||||
|
.map(|script| !script.trim().is_empty())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start the setup script execution
|
||||||
|
async fn start_setup_script(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
app_state: &crate::app_state::AppState,
|
||||||
|
attempt_id: Uuid,
|
||||||
|
task_id: Uuid,
|
||||||
|
project: &Project,
|
||||||
|
worktree_path: &str,
|
||||||
|
) -> Result<(), TaskAttemptError> {
|
||||||
|
let setup_script = project.setup_script.as_ref().unwrap();
|
||||||
|
|
||||||
|
Self::start_process_execution(
|
||||||
|
pool,
|
||||||
|
app_state,
|
||||||
|
attempt_id,
|
||||||
|
task_id,
|
||||||
|
crate::executor::ExecutorType::SetupScript(setup_script.clone()),
|
||||||
|
"Starting setup script".to_string(),
|
||||||
|
TaskAttemptStatus::SetupRunning,
|
||||||
|
crate::models::execution_process::ExecutionProcessType::SetupScript,
|
||||||
|
worktree_path,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start the coding agent after setup is complete or if no setup is needed
|
||||||
|
pub async fn start_coding_agent(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
app_state: &crate::app_state::AppState,
|
||||||
|
attempt_id: Uuid,
|
||||||
|
task_id: Uuid,
|
||||||
|
_project_id: Uuid,
|
||||||
|
) -> Result<(), TaskAttemptError> {
|
||||||
|
let task_attempt = TaskAttempt::find_by_id(pool, attempt_id)
|
||||||
|
.await?
|
||||||
|
.ok_or(TaskAttemptError::TaskNotFound)?;
|
||||||
|
|
||||||
|
let executor_config = Self::resolve_executor_config(&task_attempt.executor);
|
||||||
|
|
||||||
|
Self::start_process_execution(
|
||||||
|
pool,
|
||||||
|
app_state,
|
||||||
|
attempt_id,
|
||||||
|
task_id,
|
||||||
|
crate::executor::ExecutorType::CodingAgent(executor_config),
|
||||||
|
"Starting executor".to_string(),
|
||||||
|
TaskAttemptStatus::ExecutorRunning,
|
||||||
|
crate::models::execution_process::ExecutionProcessType::CodingAgent,
|
||||||
|
&task_attempt.worktree_path,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start a follow-up execution using the same executor type as the first process
|
||||||
|
pub async fn start_followup_execution(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
app_state: &crate::app_state::AppState,
|
||||||
|
attempt_id: Uuid,
|
||||||
|
task_id: Uuid,
|
||||||
|
project_id: Uuid,
|
||||||
|
prompt: &str,
|
||||||
|
) -> Result<(), TaskAttemptError> {
|
||||||
|
use crate::models::executor_session::ExecutorSession;
|
||||||
|
|
||||||
|
// Get task attempt
|
||||||
|
let task_attempt = TaskAttempt::find_by_id(pool, attempt_id)
|
||||||
|
.await?
|
||||||
|
.ok_or(TaskAttemptError::TaskNotFound)?;
|
||||||
|
|
||||||
|
// Find the most recent coding agent execution process to get the executor type
|
||||||
|
let execution_processes =
|
||||||
|
crate::models::execution_process::ExecutionProcess::find_by_task_attempt_id(
|
||||||
|
pool, attempt_id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let most_recent_coding_agent = execution_processes
|
||||||
|
.iter()
|
||||||
|
.rev() // Reverse to get most recent first (since they're ordered by created_at ASC)
|
||||||
|
.find(|p| {
|
||||||
|
matches!(
|
||||||
|
p.process_type,
|
||||||
|
crate::models::execution_process::ExecutionProcessType::CodingAgent
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok_or(TaskAttemptError::TaskNotFound)?; // No previous coding agent found
|
||||||
|
|
||||||
|
// Get the executor session to find the session ID
|
||||||
|
let executor_session =
|
||||||
|
ExecutorSession::find_by_execution_process_id(pool, most_recent_coding_agent.id)
|
||||||
|
.await?
|
||||||
|
.ok_or(TaskAttemptError::TaskNotFound)?; // No session found
|
||||||
|
|
||||||
|
// Determine the executor config from the stored executor_type
|
||||||
|
let executor_config = match most_recent_coding_agent.executor_type.as_deref() {
|
||||||
|
Some("claude") => crate::executor::ExecutorConfig::Claude,
|
||||||
|
Some("amp") => crate::executor::ExecutorConfig::Amp,
|
||||||
|
Some("echo") => crate::executor::ExecutorConfig::Echo,
|
||||||
|
_ => return Err(TaskAttemptError::TaskNotFound), // Invalid executor type
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the follow-up executor type
|
||||||
|
let followup_executor = crate::executor::ExecutorType::FollowUpCodingAgent {
|
||||||
|
config: executor_config,
|
||||||
|
session_id: executor_session.session_id.clone(),
|
||||||
|
prompt: prompt.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::start_process_execution(
|
||||||
|
pool,
|
||||||
|
app_state,
|
||||||
|
attempt_id,
|
||||||
|
task_id,
|
||||||
|
followup_executor,
|
||||||
|
"Starting follow-up executor".to_string(),
|
||||||
|
TaskAttemptStatus::ExecutorRunning,
|
||||||
|
crate::models::execution_process::ExecutionProcessType::CodingAgent,
|
||||||
|
&task_attempt.worktree_path,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve executor configuration from string name
|
||||||
|
fn resolve_executor_config(executor_name: &Option<String>) -> crate::executor::ExecutorConfig {
|
||||||
|
match executor_name.as_ref().map(|s| s.as_str()) {
|
||||||
|
Some("claude") => crate::executor::ExecutorConfig::Claude,
|
||||||
|
Some("amp") => crate::executor::ExecutorConfig::Amp,
|
||||||
|
_ => crate::executor::ExecutorConfig::Echo, // Default for "echo" or None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unified function to start any type of process execution
|
||||||
async fn start_process_execution(
|
async fn start_process_execution(
|
||||||
pool: &SqlitePool,
|
pool: &SqlitePool,
|
||||||
app_state: &crate::app_state::AppState,
|
app_state: &crate::app_state::AppState,
|
||||||
@@ -429,49 +575,182 @@ impl TaskAttempt {
|
|||||||
process_type: crate::models::execution_process::ExecutionProcessType,
|
process_type: crate::models::execution_process::ExecutionProcessType,
|
||||||
worktree_path: &str,
|
worktree_path: &str,
|
||||||
) -> Result<(), TaskAttemptError> {
|
) -> Result<(), TaskAttemptError> {
|
||||||
use crate::executors::SetupScriptExecutor;
|
|
||||||
use crate::models::execution_process::{CreateExecutionProcess, ExecutionProcess};
|
|
||||||
use crate::models::task_attempt_activity::{
|
|
||||||
CreateTaskAttemptActivity, TaskAttemptActivity,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create execution process record first (since activity now references it)
|
|
||||||
let process_id = Uuid::new_v4();
|
let process_id = Uuid::new_v4();
|
||||||
let (command, args) = match &executor_type {
|
|
||||||
|
// Create execution process record
|
||||||
|
let _execution_process = Self::create_execution_process_record(
|
||||||
|
pool,
|
||||||
|
attempt_id,
|
||||||
|
process_id,
|
||||||
|
&executor_type,
|
||||||
|
process_type.clone(),
|
||||||
|
worktree_path,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Create executor session for coding agents
|
||||||
|
if matches!(
|
||||||
|
process_type,
|
||||||
|
crate::models::execution_process::ExecutionProcessType::CodingAgent
|
||||||
|
) {
|
||||||
|
Self::create_executor_session_record(pool, attempt_id, task_id, process_id).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create activity record
|
||||||
|
Self::create_activity_record(pool, process_id, activity_status.clone(), &activity_note)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
tracing::info!("Starting {} for task attempt {}", activity_note, attempt_id);
|
||||||
|
|
||||||
|
// Execute the process
|
||||||
|
let child = Self::execute_process(
|
||||||
|
&executor_type,
|
||||||
|
pool,
|
||||||
|
task_id,
|
||||||
|
attempt_id,
|
||||||
|
process_id,
|
||||||
|
worktree_path,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Register for monitoring
|
||||||
|
Self::register_for_monitoring(app_state, process_id, attempt_id, &process_type, child)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
"Started execution {} for task attempt {}",
|
||||||
|
process_id,
|
||||||
|
attempt_id
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create execution process database record
|
||||||
|
async fn create_execution_process_record(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
attempt_id: Uuid,
|
||||||
|
process_id: Uuid,
|
||||||
|
executor_type: &crate::executor::ExecutorType,
|
||||||
|
process_type: crate::models::execution_process::ExecutionProcessType,
|
||||||
|
worktree_path: &str,
|
||||||
|
) -> Result<crate::models::execution_process::ExecutionProcess, TaskAttemptError> {
|
||||||
|
use crate::models::execution_process::{CreateExecutionProcess, ExecutionProcess};
|
||||||
|
|
||||||
|
let (command, args, executor_type_string) = match executor_type {
|
||||||
crate::executor::ExecutorType::SetupScript(_) => (
|
crate::executor::ExecutorType::SetupScript(_) => (
|
||||||
"bash".to_string(),
|
"bash".to_string(),
|
||||||
Some(serde_json::to_string(&["-c", "setup_script"]).unwrap()),
|
Some(serde_json::to_string(&["-c", "setup_script"]).unwrap()),
|
||||||
|
None, // Setup scripts don't have an executor type
|
||||||
),
|
),
|
||||||
crate::executor::ExecutorType::CodingAgent(_) => ("executor".to_string(), None),
|
crate::executor::ExecutorType::CodingAgent(config) => {
|
||||||
|
let executor_type_str = match config {
|
||||||
|
crate::executor::ExecutorConfig::Echo => "echo",
|
||||||
|
crate::executor::ExecutorConfig::Claude => "claude",
|
||||||
|
crate::executor::ExecutorConfig::Amp => "amp",
|
||||||
|
};
|
||||||
|
(
|
||||||
|
"executor".to_string(),
|
||||||
|
None,
|
||||||
|
Some(executor_type_str.to_string()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
crate::executor::ExecutorType::FollowUpCodingAgent { config, .. } => {
|
||||||
|
let executor_type_str = match config {
|
||||||
|
crate::executor::ExecutorConfig::Echo => "echo",
|
||||||
|
crate::executor::ExecutorConfig::Claude => "claude",
|
||||||
|
crate::executor::ExecutorConfig::Amp => "amp",
|
||||||
|
};
|
||||||
|
(
|
||||||
|
"followup_executor".to_string(),
|
||||||
|
None,
|
||||||
|
Some(executor_type_str.to_string()),
|
||||||
|
)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let create_process = CreateExecutionProcess {
|
let create_process = CreateExecutionProcess {
|
||||||
task_attempt_id: attempt_id,
|
task_attempt_id: attempt_id,
|
||||||
process_type: process_type.clone(),
|
process_type,
|
||||||
|
executor_type: executor_type_string,
|
||||||
command,
|
command,
|
||||||
args,
|
args,
|
||||||
working_directory: worktree_path.to_string(),
|
working_directory: worktree_path.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let _process = ExecutionProcess::create(pool, &create_process, process_id).await?;
|
ExecutionProcess::create(pool, &create_process, process_id)
|
||||||
|
.await
|
||||||
|
.map_err(TaskAttemptError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create executor session record for coding agents
|
||||||
|
async fn create_executor_session_record(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
attempt_id: Uuid,
|
||||||
|
task_id: Uuid,
|
||||||
|
process_id: Uuid,
|
||||||
|
) -> Result<(), TaskAttemptError> {
|
||||||
|
use crate::models::executor_session::{CreateExecutorSession, ExecutorSession};
|
||||||
|
|
||||||
|
// Get the task to create prompt
|
||||||
|
let task = Task::find_by_id(pool, task_id)
|
||||||
|
.await?
|
||||||
|
.ok_or(TaskAttemptError::TaskNotFound)?;
|
||||||
|
|
||||||
|
let prompt = format!("{}\n\n{}", task.title, task.description.unwrap_or_default());
|
||||||
|
|
||||||
|
let session_id = Uuid::new_v4();
|
||||||
|
let create_session = CreateExecutorSession {
|
||||||
|
task_attempt_id: attempt_id,
|
||||||
|
execution_process_id: process_id,
|
||||||
|
prompt: Some(prompt),
|
||||||
|
};
|
||||||
|
|
||||||
|
ExecutorSession::create(pool, &create_session, session_id)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(TaskAttemptError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create activity record for process start
|
||||||
|
async fn create_activity_record(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
process_id: Uuid,
|
||||||
|
activity_status: TaskAttemptStatus,
|
||||||
|
activity_note: &str,
|
||||||
|
) -> Result<(), TaskAttemptError> {
|
||||||
|
use crate::models::task_attempt_activity::{
|
||||||
|
CreateTaskAttemptActivity, TaskAttemptActivity,
|
||||||
|
};
|
||||||
|
|
||||||
// Create activity for process start (after process is created)
|
|
||||||
let activity_id = Uuid::new_v4();
|
let activity_id = Uuid::new_v4();
|
||||||
let create_activity = CreateTaskAttemptActivity {
|
let create_activity = CreateTaskAttemptActivity {
|
||||||
execution_process_id: process_id,
|
execution_process_id: process_id,
|
||||||
status: Some(activity_status.clone()),
|
status: Some(activity_status.clone()),
|
||||||
note: Some(activity_note.clone()),
|
note: Some(activity_note.to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
TaskAttemptActivity::create(pool, &create_activity, activity_id, activity_status.clone())
|
TaskAttemptActivity::create(pool, &create_activity, activity_id, activity_status)
|
||||||
.await?;
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(TaskAttemptError::from)
|
||||||
|
}
|
||||||
|
|
||||||
tracing::info!("Starting {} for task attempt {}", activity_note, attempt_id);
|
/// Execute the process based on type
|
||||||
|
async fn execute_process(
|
||||||
|
executor_type: &crate::executor::ExecutorType,
|
||||||
|
pool: &SqlitePool,
|
||||||
|
task_id: Uuid,
|
||||||
|
attempt_id: Uuid,
|
||||||
|
process_id: Uuid,
|
||||||
|
worktree_path: &str,
|
||||||
|
) -> Result<tokio::process::Child, TaskAttemptError> {
|
||||||
|
use crate::executors::SetupScriptExecutor;
|
||||||
|
|
||||||
// Create the appropriate executor and spawn the process
|
let result = match executor_type {
|
||||||
let child = match executor_type {
|
|
||||||
crate::executor::ExecutorType::SetupScript(script) => {
|
crate::executor::ExecutorType::SetupScript(script) => {
|
||||||
let executor = SetupScriptExecutor { script };
|
let executor = SetupScriptExecutor {
|
||||||
|
script: script.clone(),
|
||||||
|
};
|
||||||
executor
|
executor
|
||||||
.execute_streaming(pool, task_id, attempt_id, process_id, worktree_path)
|
.execute_streaming(pool, task_id, attempt_id, process_id, worktree_path)
|
||||||
.await
|
.await
|
||||||
@@ -482,10 +761,57 @@ impl TaskAttempt {
|
|||||||
.execute_streaming(pool, task_id, attempt_id, process_id, worktree_path)
|
.execute_streaming(pool, task_id, attempt_id, process_id, worktree_path)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
crate::executor::ExecutorType::FollowUpCodingAgent {
|
||||||
.map_err(|e| TaskAttemptError::Git(git2::Error::from_str(&e.to_string())))?;
|
config,
|
||||||
|
session_id,
|
||||||
|
prompt,
|
||||||
|
} => {
|
||||||
|
use crate::executors::{AmpFollowupExecutor, ClaudeFollowupExecutor};
|
||||||
|
|
||||||
// Add to running executions for monitoring
|
let executor: Box<dyn crate::executor::Executor> = match config {
|
||||||
|
crate::executor::ExecutorConfig::Claude => {
|
||||||
|
if let Some(sid) = session_id {
|
||||||
|
Box::new(ClaudeFollowupExecutor {
|
||||||
|
session_id: sid.clone(),
|
||||||
|
prompt: prompt.clone(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return Err(TaskAttemptError::TaskNotFound); // No session ID for followup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crate::executor::ExecutorConfig::Amp => {
|
||||||
|
if let Some(tid) = session_id {
|
||||||
|
Box::new(AmpFollowupExecutor {
|
||||||
|
thread_id: tid.clone(),
|
||||||
|
prompt: prompt.clone(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return Err(TaskAttemptError::TaskNotFound); // No thread ID for followup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crate::executor::ExecutorConfig::Echo => {
|
||||||
|
// Echo doesn't support followup, use regular echo
|
||||||
|
config.create_executor()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
executor
|
||||||
|
.execute_streaming(pool, task_id, attempt_id, process_id, worktree_path)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
result.map_err(|e| TaskAttemptError::Git(git2::Error::from_str(&e.to_string())))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register process for monitoring
|
||||||
|
async fn register_for_monitoring(
|
||||||
|
app_state: &crate::app_state::AppState,
|
||||||
|
process_id: Uuid,
|
||||||
|
attempt_id: Uuid,
|
||||||
|
process_type: &crate::models::execution_process::ExecutionProcessType,
|
||||||
|
child: tokio::process::Child,
|
||||||
|
) {
|
||||||
let execution_type = match process_type {
|
let execution_type = match process_type {
|
||||||
crate::models::execution_process::ExecutionProcessType::SetupScript => {
|
crate::models::execution_process::ExecutionProcessType::SetupScript => {
|
||||||
crate::app_state::ExecutionType::SetupScript
|
crate::app_state::ExecutionType::SetupScript
|
||||||
@@ -508,53 +834,6 @@ impl TaskAttempt {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
tracing::info!(
|
|
||||||
"Started execution {} for task attempt {}",
|
|
||||||
process_id,
|
|
||||||
attempt_id
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start the coding agent after setup is complete or if no setup is needed
|
|
||||||
pub async fn start_coding_agent(
|
|
||||||
pool: &SqlitePool,
|
|
||||||
app_state: &crate::app_state::AppState,
|
|
||||||
attempt_id: Uuid,
|
|
||||||
task_id: Uuid,
|
|
||||||
_project_id: Uuid,
|
|
||||||
) -> Result<(), TaskAttemptError> {
|
|
||||||
// Get the task attempt to determine executor config
|
|
||||||
let task_attempt = TaskAttempt::find_by_id(pool, attempt_id)
|
|
||||||
.await?
|
|
||||||
.ok_or(TaskAttemptError::TaskNotFound)?;
|
|
||||||
|
|
||||||
// Determine the executor config
|
|
||||||
let executor_config = if let Some(executor_name) = &task_attempt.executor {
|
|
||||||
match executor_name.as_str() {
|
|
||||||
"echo" => crate::executor::ExecutorConfig::Echo,
|
|
||||||
"claude" => crate::executor::ExecutorConfig::Claude,
|
|
||||||
"amp" => crate::executor::ExecutorConfig::Amp,
|
|
||||||
_ => crate::executor::ExecutorConfig::Echo, // Default fallback
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
crate::executor::ExecutorConfig::Echo // Default
|
|
||||||
};
|
|
||||||
|
|
||||||
Self::start_process_execution(
|
|
||||||
pool,
|
|
||||||
app_state,
|
|
||||||
attempt_id,
|
|
||||||
task_id,
|
|
||||||
crate::executor::ExecutorType::CodingAgent(executor_config),
|
|
||||||
"Starting executor".to_string(),
|
|
||||||
TaskAttemptStatus::ExecutorRunning,
|
|
||||||
crate::models::execution_process::ExecutionProcessType::CodingAgent,
|
|
||||||
&task_attempt.worktree_path,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the git diff between the base commit and the current committed worktree state
|
/// Get the git diff between the base commit and the current committed worktree state
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ use uuid::Uuid;
|
|||||||
use crate::models::{
|
use crate::models::{
|
||||||
execution_process::ExecutionProcess,
|
execution_process::ExecutionProcess,
|
||||||
task::Task,
|
task::Task,
|
||||||
task_attempt::{BranchStatus, CreateTaskAttempt, TaskAttempt, TaskAttemptStatus, WorktreeDiff},
|
task_attempt::{
|
||||||
|
BranchStatus, CreateFollowUpAttempt, CreateTaskAttempt, TaskAttempt, TaskAttemptStatus,
|
||||||
|
WorktreeDiff,
|
||||||
|
},
|
||||||
task_attempt_activity::{CreateTaskAttemptActivity, TaskAttemptActivity},
|
task_attempt_activity::{CreateTaskAttemptActivity, TaskAttemptActivity},
|
||||||
ApiResponse,
|
ApiResponse,
|
||||||
};
|
};
|
||||||
@@ -746,6 +749,53 @@ pub async fn delete_task_attempt_file(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn create_followup_attempt(
|
||||||
|
Path((project_id, task_id, attempt_id)): Path<(Uuid, Uuid, Uuid)>,
|
||||||
|
Extension(pool): Extension<SqlitePool>,
|
||||||
|
Extension(app_state): Extension<crate::app_state::AppState>,
|
||||||
|
Json(payload): Json<CreateFollowUpAttempt>,
|
||||||
|
) -> Result<ResponseJson<ApiResponse<String>>, StatusCode> {
|
||||||
|
// Verify task attempt exists
|
||||||
|
if !TaskAttempt::exists_for_task(&pool, attempt_id, task_id, project_id)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("Failed to check task attempt existence: {}", e);
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
})?
|
||||||
|
{
|
||||||
|
return Err(StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start follow-up execution asynchronously
|
||||||
|
let pool_clone = pool.clone();
|
||||||
|
let app_state_clone = app_state.clone();
|
||||||
|
let prompt = payload.prompt.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Err(e) = TaskAttempt::start_followup_execution(
|
||||||
|
&pool_clone,
|
||||||
|
&app_state_clone,
|
||||||
|
attempt_id,
|
||||||
|
task_id,
|
||||||
|
project_id,
|
||||||
|
&prompt,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::error!(
|
||||||
|
"Failed to start follow-up execution for task attempt {}: {}",
|
||||||
|
attempt_id,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(ResponseJson(ApiResponse {
|
||||||
|
success: true,
|
||||||
|
data: Some("Follow-up execution started successfully".to_string()),
|
||||||
|
message: Some("Follow-up execution started successfully".to_string()),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn task_attempts_router() -> Router {
|
pub fn task_attempts_router() -> Router {
|
||||||
use axum::routing::post;
|
use axum::routing::post;
|
||||||
|
|
||||||
@@ -799,4 +849,8 @@ pub fn task_attempts_router() -> Router {
|
|||||||
"/projects/:project_id/execution-processes/:process_id",
|
"/projects/:project_id/execution-processes/:process_id",
|
||||||
get(get_execution_process),
|
get(get_execution_process),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/projects/:project_id/tasks/:task_id/attempts/:attempt_id/follow-up",
|
||||||
|
post(create_followup_attempt),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ import {
|
|||||||
Edit,
|
Edit,
|
||||||
Trash2,
|
Trash2,
|
||||||
StopCircle,
|
StopCircle,
|
||||||
|
Send,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Chip } from "@/components/ui/chip";
|
import { Chip } from "@/components/ui/chip";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { ExecutionOutputViewer } from "./ExecutionOutputViewer";
|
import { ExecutionOutputViewer } from "./ExecutionOutputViewer";
|
||||||
import { EditorSelectionDialog } from "./EditorSelectionDialog";
|
import { EditorSelectionDialog } from "./EditorSelectionDialog";
|
||||||
|
|
||||||
@@ -146,6 +148,8 @@ export function TaskDetailsPanel({
|
|||||||
new Set()
|
new Set()
|
||||||
);
|
);
|
||||||
const [showEditorDialog, setShowEditorDialog] = useState(false);
|
const [showEditorDialog, setShowEditorDialog] = useState(false);
|
||||||
|
const [followUpMessage, setFollowUpMessage] = useState("");
|
||||||
|
const [isSendingFollowUp, setIsSendingFollowUp] = useState(false);
|
||||||
const { config } = useConfig();
|
const { config } = useConfig();
|
||||||
|
|
||||||
// Available executors
|
// Available executors
|
||||||
@@ -185,6 +189,20 @@ export function TaskDetailsPanel({
|
|||||||
);
|
);
|
||||||
}, [selectedAttempt, attemptActivities, isStopping]);
|
}, [selectedAttempt, attemptActivities, isStopping]);
|
||||||
|
|
||||||
|
// Check if follow-up should be enabled
|
||||||
|
const canSendFollowUp = useMemo(() => {
|
||||||
|
if (!selectedAttempt || attemptActivities.length === 0 || isAttemptRunning || isSendingFollowUp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need at least one completed coding agent execution
|
||||||
|
const codingAgentActivities = attemptActivities.filter(
|
||||||
|
(activity) => activity.status === "executorcomplete"
|
||||||
|
);
|
||||||
|
|
||||||
|
return codingAgentActivities.length > 0;
|
||||||
|
}, [selectedAttempt, attemptActivities, isAttemptRunning, isSendingFollowUp]);
|
||||||
|
|
||||||
// Polling for updates when attempt is running
|
// Polling for updates when attempt is running
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isAttemptRunning || !task) return;
|
if (!isAttemptRunning || !task) return;
|
||||||
@@ -410,6 +428,39 @@ export function TaskDetailsPanel({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSendFollowUp = async () => {
|
||||||
|
if (!task || !selectedAttempt || !followUpMessage.trim()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsSendingFollowUp(true);
|
||||||
|
const response = await makeRequest(
|
||||||
|
`/api/projects/${projectId}/tasks/${task.id}/attempts/${selectedAttempt.id}/follow-up`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
prompt: followUpMessage.trim(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
// Clear the message
|
||||||
|
setFollowUpMessage("");
|
||||||
|
// Refresh activities to show the new follow-up execution
|
||||||
|
fetchAttemptActivities(selectedAttempt.id);
|
||||||
|
} else {
|
||||||
|
console.error("Failed to send follow-up:", await response.text());
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to send follow-up:", err);
|
||||||
|
} finally {
|
||||||
|
setIsSendingFollowUp(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (!task) return null;
|
if (!task) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -730,13 +781,13 @@ export function TaskDetailsPanel({
|
|||||||
] && (
|
] && (
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<div
|
<div
|
||||||
className={`transition-all duration-200 ${
|
className={`transition-all duration-200 ${
|
||||||
expandedOutputs.has(
|
expandedOutputs.has(
|
||||||
activity.execution_process_id
|
activity.execution_process_id
|
||||||
)
|
)
|
||||||
? ""
|
? ""
|
||||||
: "max-h-64 overflow-hidden"
|
: "max-h-64 overflow-hidden flex flex-col justify-end"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<ExecutionOutputViewer
|
<ExecutionOutputViewer
|
||||||
executionProcess={
|
executionProcess={
|
||||||
@@ -786,32 +837,51 @@ export function TaskDetailsPanel({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer - Follow-up section */}
|
||||||
{/* <div className="border-t p-4">
|
{selectedAttempt && (
|
||||||
<div className="space-y-2">
|
<div className="border-t p-4">
|
||||||
<Label className="text-sm font-medium">
|
<div className="space-y-2">
|
||||||
Follow-up question
|
<Label className="text-sm font-medium">
|
||||||
</Label>
|
Follow-up question
|
||||||
<div className="flex gap-2">
|
</Label>
|
||||||
<Textarea
|
<div className="flex gap-2">
|
||||||
placeholder="Ask a follow-up question about this task..."
|
<Textarea
|
||||||
value={followUpMessage}
|
placeholder="Ask a follow-up question about this task..."
|
||||||
onChange={(e) => setFollowUpMessage(e.target.value)}
|
value={followUpMessage}
|
||||||
className="flex-1 min-h-[60px] resize-none"
|
onChange={(e) => setFollowUpMessage(e.target.value)}
|
||||||
/>
|
onKeyDown={(e) => {
|
||||||
<Button
|
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
|
||||||
onClick={handleSendFollowUp}
|
e.preventDefault();
|
||||||
disabled={!followUpMessage.trim()}
|
if (canSendFollowUp && followUpMessage.trim() && !isSendingFollowUp) {
|
||||||
className="self-end"
|
handleSendFollowUp();
|
||||||
>
|
}
|
||||||
<Send className="h-4 w-4" />
|
}
|
||||||
</Button>
|
}}
|
||||||
|
className="flex-1 min-h-[60px] resize-none"
|
||||||
|
disabled={!canSendFollowUp}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={handleSendFollowUp}
|
||||||
|
disabled={!canSendFollowUp || !followUpMessage.trim() || isSendingFollowUp}
|
||||||
|
className="self-end"
|
||||||
|
>
|
||||||
|
{isSendingFollowUp ? (
|
||||||
|
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-current" />
|
||||||
|
) : (
|
||||||
|
<Send className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{!canSendFollowUp
|
||||||
|
? isAttemptRunning
|
||||||
|
? "Wait for current execution to complete before asking follow-up questions"
|
||||||
|
: "Complete at least one coding agent execution to enable follow-up questions"
|
||||||
|
: "Continue the conversation with the most recent executor session"}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
Follow-up functionality coming soon
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div> */}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -12,26 +12,11 @@ export type EditorConfig = { editor_type: EditorType, custom_command: string | n
|
|||||||
|
|
||||||
export type EditorType = "vscode" | "cursor" | "windsurf" | "intellij" | "zed" | "custom";
|
export type EditorType = "vscode" | "cursor" | "windsurf" | "intellij" | "zed" | "custom";
|
||||||
|
|
||||||
|
export type EditorConstants = { editor_types: Array<EditorType>, editor_labels: Array<string>, };
|
||||||
|
|
||||||
export type ExecutorConfig = { "type": "echo" } | { "type": "claude" } | { "type": "amp" };
|
export type ExecutorConfig = { "type": "echo" } | { "type": "claude" } | { "type": "amp" };
|
||||||
|
|
||||||
// Constants for UI components
|
export type ExecutorConstants = { executor_types: Array<ExecutorConfig>, executor_labels: Array<string>, };
|
||||||
export const EXECUTOR_TYPES = ["echo", "claude", "amp"] as const;
|
|
||||||
export const EDITOR_TYPES = ["vscode", "cursor", "windsurf", "intellij", "zed", "custom"] as const;
|
|
||||||
|
|
||||||
export const EXECUTOR_LABELS = {
|
|
||||||
echo: "Echo",
|
|
||||||
claude: "Claude",
|
|
||||||
amp: "Amp"
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const EDITOR_LABELS = {
|
|
||||||
vscode: "VS Code",
|
|
||||||
cursor: "Cursor",
|
|
||||||
windsurf: "Windsurf",
|
|
||||||
intellij: "IntelliJ IDEA",
|
|
||||||
zed: "Zed",
|
|
||||||
custom: "Custom Command"
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type CreateProject = { name: string, git_repo_path: string, use_existing_repo: boolean, setup_script: string | null, };
|
export type CreateProject = { name: string, git_repo_path: string, use_existing_repo: boolean, setup_script: string | null, };
|
||||||
|
|
||||||
@@ -63,6 +48,8 @@ export type CreateTaskAttempt = { executor: string | null, };
|
|||||||
|
|
||||||
export type UpdateTaskAttempt = Record<string, never>;
|
export type UpdateTaskAttempt = Record<string, never>;
|
||||||
|
|
||||||
|
export type CreateFollowUpAttempt = { prompt: string, };
|
||||||
|
|
||||||
export type TaskAttemptActivity = { id: string, execution_process_id: string, status: TaskAttemptStatus, note: string | null, created_at: string, };
|
export type TaskAttemptActivity = { id: string, execution_process_id: string, status: TaskAttemptStatus, note: string | null, created_at: string, };
|
||||||
|
|
||||||
export type CreateTaskAttemptActivity = { execution_process_id: string, status: TaskAttemptStatus | null, note: string | null, };
|
export type CreateTaskAttemptActivity = { execution_process_id: string, status: TaskAttemptStatus | null, note: string | null, };
|
||||||
@@ -79,12 +66,49 @@ export type WorktreeDiff = { files: Array<FileDiff>, };
|
|||||||
|
|
||||||
export type BranchStatus = { is_behind: boolean, commits_behind: number, commits_ahead: number, up_to_date: boolean, merged: boolean, };
|
export type BranchStatus = { is_behind: boolean, commits_behind: number, commits_ahead: number, up_to_date: boolean, merged: boolean, };
|
||||||
|
|
||||||
export type ExecutionProcess = { id: string, task_attempt_id: string, process_type: ExecutionProcessType, status: ExecutionProcessStatus, command: string, args: string | null, working_directory: string, stdout: string | null, stderr: string | null, exit_code: bigint | null, started_at: string, completed_at: string | null, created_at: string, updated_at: string, };
|
export type ExecutionProcess = { id: string, task_attempt_id: string, process_type: ExecutionProcessType, executor_type: string | null, status: ExecutionProcessStatus, command: string, args: string | null, working_directory: string, stdout: string | null, stderr: string | null, exit_code: bigint | null, started_at: string, completed_at: string | null, created_at: string, updated_at: string, };
|
||||||
|
|
||||||
export type ExecutionProcessStatus = "running" | "completed" | "failed" | "killed";
|
export type ExecutionProcessStatus = "running" | "completed" | "failed" | "killed";
|
||||||
|
|
||||||
export type ExecutionProcessType = "setupscript" | "codingagent" | "devserver";
|
export type ExecutionProcessType = "setupscript" | "codingagent" | "devserver";
|
||||||
|
|
||||||
export type CreateExecutionProcess = { task_attempt_id: string, process_type: ExecutionProcessType, command: string, args: string | null, working_directory: string, };
|
export type CreateExecutionProcess = { task_attempt_id: string, process_type: ExecutionProcessType, executor_type: string | null, command: string, args: string | null, working_directory: string, };
|
||||||
|
|
||||||
export type UpdateExecutionProcess = { status: ExecutionProcessStatus | null, exit_code: bigint | null, completed_at: string | null, };
|
export type UpdateExecutionProcess = { status: ExecutionProcessStatus | null, exit_code: bigint | null, completed_at: string | null, };
|
||||||
|
|
||||||
|
export type ExecutorSession = { id: string, task_attempt_id: string, execution_process_id: string, session_id: string | null, prompt: string | null, created_at: string, updated_at: string, };
|
||||||
|
|
||||||
|
export type CreateExecutorSession = { task_attempt_id: string, execution_process_id: string, prompt: string | null, };
|
||||||
|
|
||||||
|
export type UpdateExecutorSession = { session_id: string | null, prompt: string | null, };
|
||||||
|
|
||||||
|
// Generated constants
|
||||||
|
export const EXECUTOR_TYPES: string[] = [
|
||||||
|
"echo",
|
||||||
|
"claude",
|
||||||
|
"amp"
|
||||||
|
];
|
||||||
|
|
||||||
|
export const EDITOR_TYPES: EditorType[] = [
|
||||||
|
"vscode",
|
||||||
|
"cursor",
|
||||||
|
"windsurf",
|
||||||
|
"intellij",
|
||||||
|
"zed",
|
||||||
|
"custom"
|
||||||
|
];
|
||||||
|
|
||||||
|
export const EXECUTOR_LABELS: Record<string, string> = {
|
||||||
|
"echo": "Echo (Test Mode)",
|
||||||
|
"claude": "Claude",
|
||||||
|
"amp": "Amp"
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EDITOR_LABELS: Record<string, string> = {
|
||||||
|
"vscode": "VS Code",
|
||||||
|
"cursor": "Cursor",
|
||||||
|
"windsurf": "Windsurf",
|
||||||
|
"intellij": "IntelliJ IDEA",
|
||||||
|
"zed": "Zed",
|
||||||
|
"custom": "Custom"
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user