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",
|
||||
"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": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -19,59 +19,64 @@
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "status!: ExecutionProcessStatus",
|
||||
"name": "executor_type",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "command",
|
||||
"name": "status!: ExecutionProcessStatus",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"name": "command",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "working_directory",
|
||||
"name": "args",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"name": "working_directory",
|
||||
"ordinal": 7,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"name": "stdout",
|
||||
"ordinal": 8,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "exit_code",
|
||||
"name": "stderr",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "exit_code",
|
||||
"ordinal": 10,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "started_at!: DateTime<Utc>",
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "completed_at?: DateTime<Utc>",
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at!: DateTime<Utc>",
|
||||
"name": "completed_at?: DateTime<Utc>",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "updated_at!: DateTime<Utc>",
|
||||
"name": "created_at!: DateTime<Utc>",
|
||||
"ordinal": 13,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "updated_at!: DateTime<Utc>",
|
||||
"ordinal": 14,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -81,6 +86,7 @@
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
@@ -94,5 +100,5 @@
|
||||
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",
|
||||
"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": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -19,68 +19,74 @@
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "status!: ExecutionProcessStatus",
|
||||
"name": "executor_type",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "command",
|
||||
"name": "status!: ExecutionProcessStatus",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"name": "command",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "working_directory",
|
||||
"name": "args",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"name": "working_directory",
|
||||
"ordinal": 7,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"name": "stdout",
|
||||
"ordinal": 8,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "exit_code",
|
||||
"name": "stderr",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "exit_code",
|
||||
"ordinal": 10,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "started_at!: DateTime<Utc>",
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "completed_at?: DateTime<Utc>",
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at!: DateTime<Utc>",
|
||||
"name": "completed_at?: DateTime<Utc>",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "updated_at!: DateTime<Utc>",
|
||||
"name": "created_at!: DateTime<Utc>",
|
||||
"ordinal": 13,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "updated_at!: DateTime<Utc>",
|
||||
"ordinal": 14,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 14
|
||||
"Right": 15
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
@@ -94,5 +100,5 @@
|
||||
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",
|
||||
"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": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -19,59 +19,64 @@
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "status!: ExecutionProcessStatus",
|
||||
"name": "executor_type",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "command",
|
||||
"name": "status!: ExecutionProcessStatus",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"name": "command",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "working_directory",
|
||||
"name": "args",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"name": "working_directory",
|
||||
"ordinal": 7,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"name": "stdout",
|
||||
"ordinal": 8,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "exit_code",
|
||||
"name": "stderr",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "exit_code",
|
||||
"ordinal": 10,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "started_at!: DateTime<Utc>",
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "completed_at?: DateTime<Utc>",
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at!: DateTime<Utc>",
|
||||
"name": "completed_at?: DateTime<Utc>",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "updated_at!: DateTime<Utc>",
|
||||
"name": "created_at!: DateTime<Utc>",
|
||||
"ordinal": 13,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "updated_at!: DateTime<Utc>",
|
||||
"ordinal": 14,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -81,6 +86,7 @@
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
@@ -94,5 +100,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "1ada5613889792cd6098da71ce2ba1ecdee7e5dc2ff8196872368fff0caa48d8"
|
||||
"hash": "9472c8fb477958167f5fae40b85ac44252468c5226b2cdd7770f027332eed6d7"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -19,59 +19,64 @@
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "status!: ExecutionProcessStatus",
|
||||
"name": "executor_type",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "command",
|
||||
"name": "status!: ExecutionProcessStatus",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"name": "command",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "working_directory",
|
||||
"name": "args",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"name": "working_directory",
|
||||
"ordinal": 7,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"name": "stdout",
|
||||
"ordinal": 8,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "exit_code",
|
||||
"name": "stderr",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "exit_code",
|
||||
"ordinal": 10,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "started_at!: DateTime<Utc>",
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "completed_at?: DateTime<Utc>",
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at!: DateTime<Utc>",
|
||||
"name": "completed_at?: DateTime<Utc>",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "updated_at!: DateTime<Utc>",
|
||||
"name": "created_at!: DateTime<Utc>",
|
||||
"ordinal": 13,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "updated_at!: DateTime<Utc>",
|
||||
"ordinal": 14,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -81,6 +86,7 @@
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
@@ -94,5 +100,5 @@
|
||||
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 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() {
|
||||
// 1. Make sure ../shared exists
|
||||
let shared_path = Path::new("../shared");
|
||||
@@ -18,7 +52,9 @@ fn main() {
|
||||
vibe_kanban::models::config::ThemeMode::decl(),
|
||||
vibe_kanban::models::config::EditorConfig::decl(),
|
||||
vibe_kanban::models::config::EditorType::decl(),
|
||||
vibe_kanban::models::config::EditorConstants::decl(),
|
||||
vibe_kanban::executor::ExecutorConfig::decl(),
|
||||
vibe_kanban::executor::ExecutorConstants::decl(),
|
||||
vibe_kanban::models::project::CreateProject::decl(),
|
||||
vibe_kanban::models::project::Project::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::CreateTaskAttempt::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::CreateTaskAttemptActivity::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::CreateExecutionProcess::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
|
||||
@@ -69,9 +109,15 @@ fn main() {
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n\n");
|
||||
|
||||
// 6. Write the consolidated types.ts
|
||||
fs::write(shared_path.join("types.ts"), format!("{HEADER}{body}"))
|
||||
.expect("unable to write types.ts");
|
||||
// 6. Add constants
|
||||
let constants = generate_constants();
|
||||
|
||||
// 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/");
|
||||
}
|
||||
|
||||
@@ -92,6 +92,11 @@ pub trait Executor: Send + Sync {
|
||||
pub enum ExecutorType {
|
||||
SetupScript(String),
|
||||
CodingAgent(ExecutorConfig),
|
||||
FollowUpCodingAgent {
|
||||
config: ExecutorConfig,
|
||||
session_id: Option<String>,
|
||||
prompt: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// Configuration for different executor types
|
||||
@@ -107,6 +112,31 @@ pub enum ExecutorConfig {
|
||||
// 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 {
|
||||
pub fn create_executor(&self) -> Box<dyn Executor> {
|
||||
match self {
|
||||
@@ -126,17 +156,45 @@ pub async fn stream_output_to_db(
|
||||
is_stdout: bool,
|
||||
) {
|
||||
use crate::models::execution_process::ExecutionProcess;
|
||||
use crate::models::executor_session::ExecutorSession;
|
||||
|
||||
let mut reader = BufReader::new(output);
|
||||
let mut line = String::new();
|
||||
let mut accumulated_output = String::new();
|
||||
let mut update_counter = 0;
|
||||
let mut session_id_parsed = false;
|
||||
|
||||
loop {
|
||||
line.clear();
|
||||
match reader.read_line(&mut line).await {
|
||||
Ok(0) => break, // EOF
|
||||
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);
|
||||
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::models::task::Task;
|
||||
|
||||
/// An executor that uses Claude CLI to process tasks
|
||||
/// An executor that uses Amp to process tasks
|
||||
pub struct AmpExecutor;
|
||||
|
||||
/// An executor that continues an Amp thread
|
||||
pub struct AmpFollowupExecutor {
|
||||
pub thread_id: String,
|
||||
pub prompt: String,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Executor for AmpExecutor {
|
||||
async fn spawn(
|
||||
@@ -53,3 +59,39 @@ impl Executor for AmpExecutor {
|
||||
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
|
||||
pub struct ClaudeExecutor;
|
||||
|
||||
/// An executor that resumes a Claude session
|
||||
pub struct ClaudeFollowupExecutor {
|
||||
pub session_id: String,
|
||||
pub prompt: String,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Executor for ClaudeExecutor {
|
||||
async fn spawn(
|
||||
@@ -49,3 +55,32 @@ impl Executor for ClaudeExecutor {
|
||||
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 setup_script;
|
||||
|
||||
pub use amp::AmpExecutor;
|
||||
pub use claude::ClaudeExecutor;
|
||||
pub use amp::{AmpExecutor, AmpFollowupExecutor};
|
||||
pub use claude::{ClaudeExecutor, ClaudeFollowupExecutor};
|
||||
pub use echo::EchoExecutor;
|
||||
pub use setup_script::SetupScriptExecutor;
|
||||
|
||||
@@ -43,6 +43,37 @@ pub enum EditorType {
|
||||
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 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
||||
@@ -53,6 +53,7 @@ pub struct ExecutionProcess {
|
||||
pub id: Uuid,
|
||||
pub task_attempt_id: Uuid,
|
||||
pub process_type: ExecutionProcessType,
|
||||
pub executor_type: Option<String>, // "echo", "claude", "amp", etc. - only for CodingAgent processes
|
||||
pub status: ExecutionProcessStatus,
|
||||
pub command: String,
|
||||
pub args: Option<String>, // JSON array of arguments
|
||||
@@ -71,6 +72,7 @@ pub struct ExecutionProcess {
|
||||
pub struct CreateExecutionProcess {
|
||||
pub task_attempt_id: Uuid,
|
||||
pub process_type: ExecutionProcessType,
|
||||
pub executor_type: Option<String>,
|
||||
pub command: String,
|
||||
pub args: Option<String>,
|
||||
pub working_directory: String,
|
||||
@@ -93,6 +95,7 @@ impl ExecutionProcess {
|
||||
id as "id!: Uuid",
|
||||
task_attempt_id as "task_attempt_id!: Uuid",
|
||||
process_type as "process_type!: ExecutionProcessType",
|
||||
executor_type,
|
||||
status as "status!: ExecutionProcessStatus",
|
||||
command,
|
||||
args,
|
||||
@@ -123,6 +126,7 @@ impl ExecutionProcess {
|
||||
id as "id!: Uuid",
|
||||
task_attempt_id as "task_attempt_id!: Uuid",
|
||||
process_type as "process_type!: ExecutionProcessType",
|
||||
executor_type,
|
||||
status as "status!: ExecutionProcessStatus",
|
||||
command,
|
||||
args,
|
||||
@@ -151,6 +155,7 @@ impl ExecutionProcess {
|
||||
id as "id!: Uuid",
|
||||
task_attempt_id as "task_attempt_id!: Uuid",
|
||||
process_type as "process_type!: ExecutionProcessType",
|
||||
executor_type,
|
||||
status as "status!: ExecutionProcessStatus",
|
||||
command,
|
||||
args,
|
||||
@@ -181,15 +186,16 @@ impl ExecutionProcess {
|
||||
sqlx::query_as!(
|
||||
ExecutionProcess,
|
||||
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,
|
||||
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
|
||||
id as "id!: Uuid",
|
||||
task_attempt_id as "task_attempt_id!: Uuid",
|
||||
process_type as "process_type!: ExecutionProcessType",
|
||||
executor_type,
|
||||
status as "status!: ExecutionProcessStatus",
|
||||
command,
|
||||
args,
|
||||
@@ -204,6 +210,7 @@ impl ExecutionProcess {
|
||||
process_id,
|
||||
data.task_attempt_id,
|
||||
data.process_type,
|
||||
data.executor_type,
|
||||
ExecutionProcessStatus::Running,
|
||||
data.command,
|
||||
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 config;
|
||||
pub mod execution_process;
|
||||
pub mod executor_session;
|
||||
pub mod project;
|
||||
pub mod task;
|
||||
pub mod task_attempt;
|
||||
|
||||
@@ -82,6 +82,12 @@ pub struct UpdateTaskAttempt {
|
||||
// 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)]
|
||||
#[ts(export)]
|
||||
pub enum DiffChunkType {
|
||||
@@ -376,10 +382,37 @@ impl TaskAttempt {
|
||||
task_id: Uuid,
|
||||
project_id: Uuid,
|
||||
) -> Result<(), TaskAttemptError> {
|
||||
use crate::models::project::Project;
|
||||
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)
|
||||
.await?
|
||||
.ok_or(TaskAttemptError::TaskNotFound)?;
|
||||
@@ -388,36 +421,149 @@ impl TaskAttempt {
|
||||
.await?
|
||||
.ok_or(TaskAttemptError::ProjectNotFound)?;
|
||||
|
||||
// Update task status to InProgress at the start of execution (during setup)
|
||||
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
|
||||
Ok((task_attempt, project))
|
||||
}
|
||||
|
||||
/// 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(
|
||||
pool: &SqlitePool,
|
||||
app_state: &crate::app_state::AppState,
|
||||
@@ -429,49 +575,182 @@ impl TaskAttempt {
|
||||
process_type: crate::models::execution_process::ExecutionProcessType,
|
||||
worktree_path: &str,
|
||||
) -> 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 (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(_) => (
|
||||
"bash".to_string(),
|
||||
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 {
|
||||
task_attempt_id: attempt_id,
|
||||
process_type: process_type.clone(),
|
||||
process_type,
|
||||
executor_type: executor_type_string,
|
||||
command,
|
||||
args,
|
||||
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 create_activity = CreateTaskAttemptActivity {
|
||||
execution_process_id: process_id,
|
||||
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())
|
||||
.await?;
|
||||
TaskAttemptActivity::create(pool, &create_activity, activity_id, activity_status)
|
||||
.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 child = match executor_type {
|
||||
let result = match executor_type {
|
||||
crate::executor::ExecutorType::SetupScript(script) => {
|
||||
let executor = SetupScriptExecutor { script };
|
||||
let executor = SetupScriptExecutor {
|
||||
script: script.clone(),
|
||||
};
|
||||
executor
|
||||
.execute_streaming(pool, task_id, attempt_id, process_id, worktree_path)
|
||||
.await
|
||||
@@ -482,10 +761,57 @@ impl TaskAttempt {
|
||||
.execute_streaming(pool, task_id, attempt_id, process_id, worktree_path)
|
||||
.await
|
||||
}
|
||||
}
|
||||
.map_err(|e| TaskAttemptError::Git(git2::Error::from_str(&e.to_string())))?;
|
||||
crate::executor::ExecutorType::FollowUpCodingAgent {
|
||||
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 {
|
||||
crate::models::execution_process::ExecutionProcessType::SetupScript => {
|
||||
crate::app_state::ExecutionType::SetupScript
|
||||
@@ -508,53 +834,6 @@ impl TaskAttempt {
|
||||
},
|
||||
)
|
||||
.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
|
||||
|
||||
@@ -13,7 +13,10 @@ use uuid::Uuid;
|
||||
use crate::models::{
|
||||
execution_process::ExecutionProcess,
|
||||
task::Task,
|
||||
task_attempt::{BranchStatus, CreateTaskAttempt, TaskAttempt, TaskAttemptStatus, WorktreeDiff},
|
||||
task_attempt::{
|
||||
BranchStatus, CreateFollowUpAttempt, CreateTaskAttempt, TaskAttempt, TaskAttemptStatus,
|
||||
WorktreeDiff,
|
||||
},
|
||||
task_attempt_activity::{CreateTaskAttemptActivity, TaskAttemptActivity},
|
||||
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 {
|
||||
use axum::routing::post;
|
||||
|
||||
@@ -799,4 +849,8 @@ pub fn task_attempts_router() -> Router {
|
||||
"/projects/:project_id/execution-processes/:process_id",
|
||||
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,
|
||||
Trash2,
|
||||
StopCircle,
|
||||
Send,
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Chip } from "@/components/ui/chip";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { ExecutionOutputViewer } from "./ExecutionOutputViewer";
|
||||
import { EditorSelectionDialog } from "./EditorSelectionDialog";
|
||||
|
||||
@@ -146,6 +148,8 @@ export function TaskDetailsPanel({
|
||||
new Set()
|
||||
);
|
||||
const [showEditorDialog, setShowEditorDialog] = useState(false);
|
||||
const [followUpMessage, setFollowUpMessage] = useState("");
|
||||
const [isSendingFollowUp, setIsSendingFollowUp] = useState(false);
|
||||
const { config } = useConfig();
|
||||
|
||||
// Available executors
|
||||
@@ -185,6 +189,20 @@ export function TaskDetailsPanel({
|
||||
);
|
||||
}, [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
|
||||
useEffect(() => {
|
||||
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;
|
||||
|
||||
return (
|
||||
@@ -730,13 +781,13 @@ export function TaskDetailsPanel({
|
||||
] && (
|
||||
<div className="mt-2">
|
||||
<div
|
||||
className={`transition-all duration-200 ${
|
||||
expandedOutputs.has(
|
||||
activity.execution_process_id
|
||||
)
|
||||
? ""
|
||||
: "max-h-64 overflow-hidden"
|
||||
}`}
|
||||
className={`transition-all duration-200 ${
|
||||
expandedOutputs.has(
|
||||
activity.execution_process_id
|
||||
)
|
||||
? ""
|
||||
: "max-h-64 overflow-hidden flex flex-col justify-end"
|
||||
}`}
|
||||
>
|
||||
<ExecutionOutputViewer
|
||||
executionProcess={
|
||||
@@ -786,32 +837,51 @@ export function TaskDetailsPanel({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
{/* <div className="border-t p-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium">
|
||||
Follow-up question
|
||||
</Label>
|
||||
<div className="flex gap-2">
|
||||
<Textarea
|
||||
placeholder="Ask a follow-up question about this task..."
|
||||
value={followUpMessage}
|
||||
onChange={(e) => setFollowUpMessage(e.target.value)}
|
||||
className="flex-1 min-h-[60px] resize-none"
|
||||
/>
|
||||
<Button
|
||||
onClick={handleSendFollowUp}
|
||||
disabled={!followUpMessage.trim()}
|
||||
className="self-end"
|
||||
>
|
||||
<Send className="h-4 w-4" />
|
||||
</Button>
|
||||
{/* Footer - Follow-up section */}
|
||||
{selectedAttempt && (
|
||||
<div className="border-t p-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium">
|
||||
Follow-up question
|
||||
</Label>
|
||||
<div className="flex gap-2">
|
||||
<Textarea
|
||||
placeholder="Ask a follow-up question about this task..."
|
||||
value={followUpMessage}
|
||||
onChange={(e) => setFollowUpMessage(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (canSendFollowUp && followUpMessage.trim() && !isSendingFollowUp) {
|
||||
handleSendFollowUp();
|
||||
}
|
||||
}
|
||||
}}
|
||||
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>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Follow-up functionality coming soon
|
||||
</p>
|
||||
</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 EditorConstants = { editor_types: Array<EditorType>, editor_labels: Array<string>, };
|
||||
|
||||
export type ExecutorConfig = { "type": "echo" } | { "type": "claude" } | { "type": "amp" };
|
||||
|
||||
// Constants for UI components
|
||||
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 ExecutorConstants = { executor_types: Array<ExecutorConfig>, executor_labels: Array<string>, };
|
||||
|
||||
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 CreateFollowUpAttempt = { prompt: 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, };
|
||||
@@ -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 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 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