diff --git a/backend/.sqlx/query-52293d5438887ad86a1416abe78a1e68424426af5bf29db3afdafb6202ca015f.json b/backend/.sqlx/query-52293d5438887ad86a1416abe78a1e68424426af5bf29db3afdafb6202ca015f.json deleted file mode 100644 index 0035f5b1..00000000 --- a/backend/.sqlx/query-52293d5438887ad86a1416abe78a1e68424426af5bf29db3afdafb6202ca015f.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT INTO task_attempt_activities (id, execution_process_id, status, note) \n VALUES ($1, $2, $3, $4) \n RETURNING id as \"id!: Uuid\", execution_process_id as \"execution_process_id!: Uuid\", status as \"status!: TaskAttemptStatus\", note, created_at as \"created_at!: DateTime\"", - "describe": { - "columns": [ - { - "name": "id!: Uuid", - "ordinal": 0, - "type_info": "Text" - }, - { - "name": "execution_process_id!: Uuid", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "status!: TaskAttemptStatus", - "ordinal": 2, - "type_info": "Text" - }, - { - "name": "note", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "created_at!: DateTime", - "ordinal": 4, - "type_info": "Datetime" - } - ], - "parameters": { - "Right": 4 - }, - "nullable": [ - true, - false, - false, - true, - false - ] - }, - "hash": "52293d5438887ad86a1416abe78a1e68424426af5bf29db3afdafb6202ca015f" -} diff --git a/backend/.sqlx/query-807dcfd9652232f7908466a513eab167592a20b0d857fe97642dc8a34f3ca170.json b/backend/.sqlx/query-807dcfd9652232f7908466a513eab167592a20b0d857fe97642dc8a34f3ca170.json deleted file mode 100644 index d0375bcb..00000000 --- a/backend/.sqlx/query-807dcfd9652232f7908466a513eab167592a20b0d857fe97642dc8a34f3ca170.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT \n t.id AS \"id!: Uuid\", \n t.project_id AS \"project_id!: Uuid\", \n t.title, \n t.description, \n t.status AS \"status!: TaskStatus\", \n t.parent_task_attempt AS \"parent_task_attempt: Uuid\", \n t.created_at AS \"created_at!: DateTime\", \n t.updated_at AS \"updated_at!: DateTime\",\n CASE \n WHEN in_progress_attempts.task_id IS NOT NULL THEN true \n ELSE false \n END AS \"has_in_progress_attempt!: i64\",\n CASE \n WHEN merged_attempts.task_id IS NOT NULL THEN true \n ELSE false \n END AS \"has_merged_attempt!\",\n CASE \n WHEN failed_attempts.task_id IS NOT NULL THEN true \n ELSE false \n END AS \"has_failed_attempt!\",\n latest_executor_attempts.executor AS \"latest_attempt_executor\"\n FROM tasks t\n LEFT JOIN (\n SELECT DISTINCT ta.task_id\n FROM task_attempts ta\n JOIN execution_processes ep \n ON ta.id = ep.task_attempt_id\n JOIN (\n -- pick exactly one “latest” activity per process,\n -- tiebreaking so that running‐states are lower priority\n SELECT execution_process_id, status\n FROM (\n SELECT\n execution_process_id,\n status,\n ROW_NUMBER() OVER (\n PARTITION BY execution_process_id\n ORDER BY\n created_at DESC,\n CASE \n WHEN status IN ('setuprunning','executorrunning') THEN 1 \n ELSE 0 \n END\n ) AS rn\n FROM task_attempt_activities\n ) sub\n WHERE rn = 1\n ) latest_act \n ON ep.id = latest_act.execution_process_id\n WHERE latest_act.status IN ('setuprunning','executorrunning')\n ) in_progress_attempts \n ON t.id = in_progress_attempts.task_id\n LEFT JOIN (\n SELECT DISTINCT ta.task_id\n FROM task_attempts ta\n WHERE ta.merge_commit IS NOT NULL\n ) merged_attempts \n ON t.id = merged_attempts.task_id\n LEFT JOIN (\n SELECT DISTINCT latest_attempts.task_id\n FROM (\n -- Get the latest attempt for each task\n SELECT task_id, id as attempt_id, created_at,\n ROW_NUMBER() OVER (PARTITION BY task_id ORDER BY created_at DESC) AS rn\n FROM task_attempts\n WHERE merge_commit IS NULL -- Don't show as failed if already merged\n ) latest_attempts\n JOIN execution_processes ep \n ON latest_attempts.attempt_id = ep.task_attempt_id\n JOIN (\n -- pick exactly one \"latest\" activity per process,\n -- tiebreaking so that running‐states are lower priority\n SELECT execution_process_id, status\n FROM (\n SELECT\n execution_process_id,\n status,\n ROW_NUMBER() OVER (\n PARTITION BY execution_process_id\n ORDER BY\n created_at DESC,\n CASE \n WHEN status IN ('setuprunning','executorrunning') THEN 1 \n ELSE 0 \n END\n ) AS rn\n FROM task_attempt_activities\n ) sub\n WHERE rn = 1\n ) latest_act \n ON ep.id = latest_act.execution_process_id\n WHERE latest_attempts.rn = 1 -- Only consider the latest attempt\n AND latest_act.status IN ('setupfailed','executorfailed')\n ) failed_attempts \n ON t.id = failed_attempts.task_id\n LEFT JOIN (\n SELECT task_id, executor\n FROM (\n SELECT task_id, executor, created_at,\n ROW_NUMBER() OVER (PARTITION BY task_id ORDER BY created_at DESC) AS rn\n FROM task_attempts\n ) latest_attempts\n WHERE rn = 1\n ) latest_executor_attempts \n ON t.id = latest_executor_attempts.task_id\n WHERE t.project_id = $1\n ORDER BY t.created_at DESC;\n ", - "describe": { - "columns": [ - { - "name": "id!: Uuid", - "ordinal": 0, - "type_info": "Blob" - }, - { - "name": "project_id!: Uuid", - "ordinal": 1, - "type_info": "Blob" - }, - { - "name": "title", - "ordinal": 2, - "type_info": "Text" - }, - { - "name": "description", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "status!: TaskStatus", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "parent_task_attempt: Uuid", - "ordinal": 5, - "type_info": "Blob" - }, - { - "name": "created_at!: DateTime", - "ordinal": 6, - "type_info": "Text" - }, - { - "name": "updated_at!: DateTime", - "ordinal": 7, - "type_info": "Text" - }, - { - "name": "has_in_progress_attempt!: i64", - "ordinal": 8, - "type_info": "Integer" - }, - { - "name": "has_merged_attempt!", - "ordinal": 9, - "type_info": "Integer" - }, - { - "name": "has_failed_attempt!", - "ordinal": 10, - "type_info": "Integer" - }, - { - "name": "latest_attempt_executor", - "ordinal": 11, - "type_info": "Text" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - true, - false, - false, - true, - false, - true, - false, - false, - false, - false, - false, - true - ] - }, - "hash": "807dcfd9652232f7908466a513eab167592a20b0d857fe97642dc8a34f3ca170" -} diff --git a/backend/.sqlx/query-94a21be956c9451a8b117d25ffd4e5ee75bba0aa032139572cf87651e2856f3a.json b/backend/.sqlx/query-94a21be956c9451a8b117d25ffd4e5ee75bba0aa032139572cf87651e2856f3a.json new file mode 100644 index 00000000..a4894947 --- /dev/null +++ b/backend/.sqlx/query-94a21be956c9451a8b117d25ffd4e5ee75bba0aa032139572cf87651e2856f3a.json @@ -0,0 +1,86 @@ +{ + "db_name": "SQLite", + "query": "SELECT \n t.id AS \"id!: Uuid\",\n t.project_id AS \"project_id!: Uuid\",\n t.title,\n t.description,\n t.status AS \"status!: TaskStatus\",\n t.parent_task_attempt AS \"parent_task_attempt: Uuid\", \n t.created_at AS \"created_at!: DateTime\",\n t.updated_at AS \"updated_at!: DateTime\",\n CASE \n WHEN ip.task_id IS NOT NULL THEN true \n ELSE false \n END AS \"has_in_progress_attempt!: i64\",\n CASE \n WHEN ma.task_id IS NOT NULL THEN true \n ELSE false \n END AS \"has_merged_attempt!: i64\",\n CASE \n WHEN fa.task_id IS NOT NULL THEN true \n ELSE false \n END AS \"has_failed_attempt!: i64\",\n latest_executor_attempts.executor AS \"latest_attempt_executor\"\n FROM tasks t\n\n -- in-progress if any running setupscript/codingagent\n LEFT JOIN (\n SELECT DISTINCT ta.task_id\n FROM task_attempts ta\n JOIN execution_processes ep \n ON ta.id = ep.task_attempt_id\n WHERE ep.status = 'running'\n AND ep.process_type IN ('setupscript','codingagent')\n ) ip \n ON t.id = ip.task_id\n\n -- merged if merge_commit not null\n LEFT JOIN (\n SELECT DISTINCT task_id\n FROM task_attempts\n WHERE merge_commit IS NOT NULL\n ) ma \n ON t.id = ma.task_id\n\n -- failed if latest attempt has a failed setupscript/codingagent\n LEFT JOIN (\n SELECT sub.task_id\n FROM (\n SELECT\n ta.task_id,\n ep.status,\n ep.process_type,\n ROW_NUMBER() OVER (\n PARTITION BY ta.task_id \n ORDER BY ta.created_at DESC\n ) AS rn\n FROM task_attempts ta\n JOIN execution_processes ep \n ON ta.id = ep.task_attempt_id\n WHERE ep.process_type IN ('setupscript','codingagent')\n ) sub\n WHERE sub.rn = 1\n AND sub.status IN ('failed','killed')\n ) fa\n ON t.id = fa.task_id\n\n -- get the executor of the latest attempt\n LEFT JOIN (\n SELECT task_id, executor\n FROM (\n SELECT task_id, executor, created_at,\n ROW_NUMBER() OVER (PARTITION BY task_id ORDER BY created_at DESC) AS rn\n FROM task_attempts\n ) latest_attempts\n WHERE rn = 1\n ) latest_executor_attempts \n ON t.id = latest_executor_attempts.task_id\n\n WHERE t.project_id = $1\n ORDER BY t.created_at DESC;\n ", + "describe": { + "columns": [ + { + "name": "id!: Uuid", + "ordinal": 0, + "type_info": "Blob" + }, + { + "name": "project_id!: Uuid", + "ordinal": 1, + "type_info": "Blob" + }, + { + "name": "title", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "description", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "status!: TaskStatus", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "parent_task_attempt: Uuid", + "ordinal": 5, + "type_info": "Blob" + }, + { + "name": "created_at!: DateTime", + "ordinal": 6, + "type_info": "Text" + }, + { + "name": "updated_at!: DateTime", + "ordinal": 7, + "type_info": "Text" + }, + { + "name": "has_in_progress_attempt!: i64", + "ordinal": 8, + "type_info": "Integer" + }, + { + "name": "has_merged_attempt!: i64", + "ordinal": 9, + "type_info": "Integer" + }, + { + "name": "has_failed_attempt!: i64", + "ordinal": 10, + "type_info": "Integer" + }, + { + "name": "latest_attempt_executor", + "ordinal": 11, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + true, + false, + false, + true, + false, + true, + false, + false, + false, + false, + false, + true + ] + }, + "hash": "94a21be956c9451a8b117d25ffd4e5ee75bba0aa032139572cf87651e2856f3a" +} diff --git a/backend/.sqlx/query-b029d6c1a0ccbd90e54c0e948dbb5623c650f935918e65ec34644210be44c0b1.json b/backend/.sqlx/query-b029d6c1a0ccbd90e54c0e948dbb5623c650f935918e65ec34644210be44c0b1.json deleted file mode 100644 index e5b116fd..00000000 --- a/backend/.sqlx/query-b029d6c1a0ccbd90e54c0e948dbb5623c650f935918e65ec34644210be44c0b1.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT DISTINCT ep.id as \"id!: Uuid\"\n FROM execution_processes ep\n INNER JOIN (\n SELECT execution_process_id, MAX(created_at) as latest_created_at\n FROM task_attempt_activities\n GROUP BY execution_process_id\n ) latest_activity ON ep.id = latest_activity.execution_process_id\n INNER JOIN task_attempt_activities taa ON ep.id = taa.execution_process_id \n AND taa.created_at = latest_activity.latest_created_at\n WHERE taa.status IN ($1, $2)", - "describe": { - "columns": [ - { - "name": "id!: Uuid", - "ordinal": 0, - "type_info": "Blob" - } - ], - "parameters": { - "Right": 2 - }, - "nullable": [ - true - ] - }, - "hash": "b029d6c1a0ccbd90e54c0e948dbb5623c650f935918e65ec34644210be44c0b1" -} diff --git a/backend/.sqlx/query-bd57e47989627ac5e9f44e83f4bacff590d357586b149c7c17ff0e8f349e7025.json b/backend/.sqlx/query-bd57e47989627ac5e9f44e83f4bacff590d357586b149c7c17ff0e8f349e7025.json deleted file mode 100644 index 09e2a088..00000000 --- a/backend/.sqlx/query-bd57e47989627ac5e9f44e83f4bacff590d357586b149c7c17ff0e8f349e7025.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT \n taa.id as \"activity_id!: Uuid\",\n taa.execution_process_id as \"execution_process_id!: Uuid\",\n taa.status as \"status!: TaskAttemptStatus\",\n taa.note,\n taa.created_at as \"created_at!: DateTime\",\n es.prompt\n FROM task_attempt_activities taa\n INNER JOIN execution_processes ep ON taa.execution_process_id = ep.id\n LEFT JOIN executor_sessions es ON es.execution_process_id = ep.id\n WHERE ep.task_attempt_id = $1\n ORDER BY taa.created_at ASC", - "describe": { - "columns": [ - { - "name": "activity_id!: Uuid", - "ordinal": 0, - "type_info": "Text" - }, - { - "name": "execution_process_id!: Uuid", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "status!: TaskAttemptStatus", - "ordinal": 2, - "type_info": "Text" - }, - { - "name": "note", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "created_at!: DateTime", - "ordinal": 4, - "type_info": "Datetime" - }, - { - "name": "prompt", - "ordinal": 5, - "type_info": "Text" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - true, - false, - false, - true, - false, - true - ] - }, - "hash": "bd57e47989627ac5e9f44e83f4bacff590d357586b149c7c17ff0e8f349e7025" -} diff --git a/backend/.sqlx/query-cb56bae2e03ef702cd8737f072d9c73957cc04695741fd7788370dfd9184548a.json b/backend/.sqlx/query-cb56bae2e03ef702cd8737f072d9c73957cc04695741fd7788370dfd9184548a.json deleted file mode 100644 index 5e0f192a..00000000 --- a/backend/.sqlx/query-cb56bae2e03ef702cd8737f072d9c73957cc04695741fd7788370dfd9184548a.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT id as \"id!: Uuid\", execution_process_id as \"execution_process_id!: Uuid\", status as \"status!: TaskAttemptStatus\", note, created_at as \"created_at!: DateTime\"\n FROM task_attempt_activities \n WHERE execution_process_id = $1 \n ORDER BY created_at DESC", - "describe": { - "columns": [ - { - "name": "id!: Uuid", - "ordinal": 0, - "type_info": "Text" - }, - { - "name": "execution_process_id!: Uuid", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "status!: TaskAttemptStatus", - "ordinal": 2, - "type_info": "Text" - }, - { - "name": "note", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "created_at!: DateTime", - "ordinal": 4, - "type_info": "Datetime" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - true, - false, - false, - true, - false - ] - }, - "hash": "cb56bae2e03ef702cd8737f072d9c73957cc04695741fd7788370dfd9184548a" -} diff --git a/backend/migrations/20250717000000_drop_task_attempt_activities.sql b/backend/migrations/20250717000000_drop_task_attempt_activities.sql new file mode 100644 index 00000000..d272f7ef --- /dev/null +++ b/backend/migrations/20250717000000_drop_task_attempt_activities.sql @@ -0,0 +1,9 @@ +-- Migration to drop task_attempt_activities table +-- This removes the task attempt activity tracking functionality + +-- Drop indexes first +DROP INDEX IF EXISTS idx_task_attempt_activities_execution_process_id; +DROP INDEX IF EXISTS idx_task_attempt_activities_created_at; + +-- Drop the table +DROP TABLE IF EXISTS task_attempt_activities; diff --git a/backend/src/bin/generate_types.rs b/backend/src/bin/generate_types.rs index 024d5934..dd423a36 100644 --- a/backend/src/bin/generate_types.rs +++ b/backend/src/bin/generate_types.rs @@ -108,9 +108,6 @@ fn generate_types_content() -> String { 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::TaskAttemptActivityWithPrompt::decl(), - vibe_kanban::models::task_attempt_activity::CreateTaskAttemptActivity::decl(), vibe_kanban::routes::filesystem::DirectoryEntry::decl(), vibe_kanban::routes::filesystem::DirectoryListResponse::decl(), vibe_kanban::routes::auth::DeviceStartResponse::decl(), diff --git a/backend/src/execution_monitor.rs b/backend/src/execution_monitor.rs index 5c49c3f9..ee5c30ff 100644 --- a/backend/src/execution_monitor.rs +++ b/backend/src/execution_monitor.rs @@ -6,8 +6,7 @@ use crate::{ models::{ execution_process::{ExecutionProcess, ExecutionProcessStatus, ExecutionProcessType}, task::{Task, TaskStatus}, - task_attempt::{TaskAttempt, TaskAttemptStatus}, - task_attempt_activity::{CreateTaskAttemptActivity, TaskAttemptActivity}, + task_attempt::TaskAttempt, }, services::{NotificationConfig, NotificationService, ProcessService}, utils::worktree_manager::WorktreeManager, @@ -573,10 +572,8 @@ pub async fn execution_monitor(app_state: AppState) { handle_setup_completion( &app_state, task_attempt_id, - execution_process_id, execution_process, success, - exit_code, ) .await; } @@ -669,30 +666,7 @@ pub async fn execution_monitor(app_state: AppState) { continue; } - // Create task attempt activity for non-dev server processes - if process.process_type != ExecutionProcessType::DevServer { - let activity_id = Uuid::new_v4(); - let create_activity = CreateTaskAttemptActivity { - execution_process_id: process.id, - status: Some(TaskAttemptStatus::ExecutorFailed), - note: Some("Execution lost (server restart or crash)".to_string()), - }; - - if let Err(e) = TaskAttemptActivity::create( - &app_state.db_pool, - &create_activity, - activity_id, - TaskAttemptStatus::ExecutorFailed, - ) - .await - { - tracing::error!( - "Failed to create failed activity for orphaned process: {}", - e - ); - continue; - } - } + // Process marked as failed tracing::info!("Marked orphaned execution process {} as failed", process.id); @@ -766,17 +740,9 @@ pub async fn execution_monitor(app_state: AppState) { async fn handle_setup_completion( app_state: &AppState, task_attempt_id: Uuid, - execution_process_id: Uuid, execution_process: ExecutionProcess, success: bool, - exit_code: Option, ) { - let exit_text = if let Some(code) = exit_code { - format!(" with exit code {}", code) - } else { - String::new() - }; - if success { // Mark setup as completed in database if let Err(e) = TaskAttempt::mark_setup_completed(&app_state.db_pool, task_attempt_id).await @@ -788,25 +754,7 @@ async fn handle_setup_completion( ); } - // Setup completed successfully, create activity - let activity_id = Uuid::new_v4(); - let create_activity = CreateTaskAttemptActivity { - execution_process_id, - status: Some(TaskAttemptStatus::SetupComplete), - note: Some(format!("Setup script completed successfully{}", exit_text)), - }; - - if let Err(e) = TaskAttemptActivity::create( - &app_state.db_pool, - &create_activity, - activity_id, - TaskAttemptStatus::SetupComplete, - ) - .await - { - tracing::error!("Failed to create setup complete activity: {}", e); - return; - } + // Setup completed successfully // Check for delegation context in process args let delegation_result = if let Some(args_json) = &execution_process.args { @@ -845,24 +793,7 @@ async fn handle_setup_completion( } } } else { - // Setup failed, create activity and update task status - let activity_id = Uuid::new_v4(); - let create_activity = CreateTaskAttemptActivity { - execution_process_id, - status: Some(TaskAttemptStatus::SetupFailed), - note: Some(format!("Setup script failed{}", exit_text)), - }; - - if let Err(e) = TaskAttemptActivity::create( - &app_state.db_pool, - &create_activity, - activity_id, - TaskAttemptStatus::SetupFailed, - ) - .await - { - tracing::error!("Failed to create setup failed activity: {}", e); - } + // Setup failed, update task status // Update task status to InReview since setup failed if let Ok(Some(task_attempt)) = @@ -897,12 +828,6 @@ async fn handle_coding_agent_completion( success: bool, exit_code: Option, ) { - let exit_text = if let Some(code) = exit_code { - format!(" with exit code {}", code) - } else { - String::new() - }; - // Extract and store assistant message from execution logs let summary = if let Some(stdout) = &execution_process.stdout { if let Some(assistant_message) = crate::executor::parse_assistant_message_from_logs(stdout) @@ -1020,60 +945,40 @@ async fn handle_coding_agent_completion( ); } - // Create task attempt activity with appropriate completion status - let activity_id = Uuid::new_v4(); - let status = if success { - TaskAttemptStatus::ExecutorComplete - } else { - TaskAttemptStatus::ExecutorFailed - }; - let create_activity = CreateTaskAttemptActivity { - execution_process_id, - status: Some(status.clone()), - note: Some(format!("Coding agent execution completed{}", exit_text)), - }; + // Coding agent execution completed + tracing::info!( + "Task attempt {} set to paused after coding agent completion", + task_attempt_id + ); - if let Err(e) = - TaskAttemptActivity::create(&app_state.db_pool, &create_activity, activity_id, status) - .await - { - tracing::error!("Failed to create executor completion activity: {}", e); - } else { - tracing::info!( - "Task attempt {} set to paused after coding agent completion", - task_attempt_id - ); - - // Get task to access task_id and project_id for status update - if let Ok(Some(task)) = Task::find_by_id(&app_state.db_pool, task_attempt.task_id).await - { - app_state - .track_analytics_event( - "task_attempt_finished", - Some(serde_json::json!({ - "task_id": task.id.to_string(), - "project_id": task.project_id.to_string(), - "attempt_id": task_attempt_id.to_string(), - "execution_success": success, - "exit_code": exit_code, - })), - ) - .await; - - // Update task status to InReview - if let Err(e) = Task::update_status( - &app_state.db_pool, - task.id, - task.project_id, - TaskStatus::InReview, + // Get task to access task_id and project_id for status update + if let Ok(Some(task)) = Task::find_by_id(&app_state.db_pool, task_attempt.task_id).await { + app_state + .track_analytics_event( + "task_attempt_finished", + Some(serde_json::json!({ + "task_id": task.id.to_string(), + "project_id": task.project_id.to_string(), + "attempt_id": task_attempt_id.to_string(), + "execution_success": success, + "exit_code": exit_code, + })), ) - .await - { - tracing::error!( - "Failed to update task status to InReview for completed attempt: {}", - e - ); - } + .await; + + // Update task status to InReview + if let Err(e) = Task::update_status( + &app_state.db_pool, + task.id, + task.project_id, + TaskStatus::InReview, + ) + .await + { + tracing::error!( + "Failed to update task status to InReview for completed attempt: {}", + e + ); } } } else { diff --git a/backend/src/models/mod.rs b/backend/src/models/mod.rs index b7eb6a54..79b25be0 100644 --- a/backend/src/models/mod.rs +++ b/backend/src/models/mod.rs @@ -5,7 +5,7 @@ pub mod executor_session; pub mod project; pub mod task; pub mod task_attempt; -pub mod task_attempt_activity; + pub mod task_template; pub use api_response::ApiResponse; diff --git a/backend/src/models/task.rs b/backend/src/models/task.rs index f17edbd8..ed10ea16 100644 --- a/backend/src/models/task.rs +++ b/backend/src/models/task.rs @@ -81,114 +81,85 @@ impl Task { ) -> Result, sqlx::Error> { let records = sqlx::query!( r#"SELECT - t.id AS "id!: Uuid", - t.project_id AS "project_id!: Uuid", - t.title, - t.description, - t.status AS "status!: TaskStatus", - t.parent_task_attempt AS "parent_task_attempt: Uuid", - t.created_at AS "created_at!: DateTime", - t.updated_at AS "updated_at!: DateTime", - CASE - WHEN in_progress_attempts.task_id IS NOT NULL THEN true - ELSE false - END AS "has_in_progress_attempt!: i64", - CASE - WHEN merged_attempts.task_id IS NOT NULL THEN true - ELSE false - END AS "has_merged_attempt!", - CASE - WHEN failed_attempts.task_id IS NOT NULL THEN true - ELSE false - END AS "has_failed_attempt!", - latest_executor_attempts.executor AS "latest_attempt_executor" - FROM tasks t - LEFT JOIN ( - SELECT DISTINCT ta.task_id + t.id AS "id!: Uuid", + t.project_id AS "project_id!: Uuid", + t.title, + t.description, + t.status AS "status!: TaskStatus", + t.parent_task_attempt AS "parent_task_attempt: Uuid", + t.created_at AS "created_at!: DateTime", + t.updated_at AS "updated_at!: DateTime", + CASE + WHEN ip.task_id IS NOT NULL THEN true + ELSE false + END AS "has_in_progress_attempt!: i64", + CASE + WHEN ma.task_id IS NOT NULL THEN true + ELSE false + END AS "has_merged_attempt!: i64", + CASE + WHEN fa.task_id IS NOT NULL THEN true + ELSE false + END AS "has_failed_attempt!: i64", + latest_executor_attempts.executor AS "latest_attempt_executor" + FROM tasks t + + -- in-progress if any running setupscript/codingagent + LEFT JOIN ( + SELECT DISTINCT ta.task_id + FROM task_attempts ta + JOIN execution_processes ep + ON ta.id = ep.task_attempt_id + WHERE ep.status = 'running' + AND ep.process_type IN ('setupscript','codingagent') + ) ip + ON t.id = ip.task_id + + -- merged if merge_commit not null + LEFT JOIN ( + SELECT DISTINCT task_id + FROM task_attempts + WHERE merge_commit IS NOT NULL + ) ma + ON t.id = ma.task_id + + -- failed if latest attempt has a failed setupscript/codingagent + LEFT JOIN ( + SELECT sub.task_id + FROM ( + SELECT + ta.task_id, + ep.status, + ep.process_type, + ROW_NUMBER() OVER ( + PARTITION BY ta.task_id + ORDER BY ta.created_at DESC + ) AS rn FROM task_attempts ta JOIN execution_processes ep - ON ta.id = ep.task_attempt_id - JOIN ( - -- pick exactly one “latest” activity per process, - -- tiebreaking so that running‐states are lower priority - SELECT execution_process_id, status - FROM ( - SELECT - execution_process_id, - status, - ROW_NUMBER() OVER ( - PARTITION BY execution_process_id - ORDER BY - created_at DESC, - CASE - WHEN status IN ('setuprunning','executorrunning') THEN 1 - ELSE 0 - END - ) AS rn - FROM task_attempt_activities - ) sub - WHERE rn = 1 - ) latest_act - ON ep.id = latest_act.execution_process_id - WHERE latest_act.status IN ('setuprunning','executorrunning') - ) in_progress_attempts - ON t.id = in_progress_attempts.task_id - LEFT JOIN ( - SELECT DISTINCT ta.task_id - FROM task_attempts ta - WHERE ta.merge_commit IS NOT NULL - ) merged_attempts - ON t.id = merged_attempts.task_id - LEFT JOIN ( - SELECT DISTINCT latest_attempts.task_id - FROM ( - -- Get the latest attempt for each task - SELECT task_id, id as attempt_id, created_at, - ROW_NUMBER() OVER (PARTITION BY task_id ORDER BY created_at DESC) AS rn - FROM task_attempts - WHERE merge_commit IS NULL -- Don't show as failed if already merged - ) latest_attempts - JOIN execution_processes ep - ON latest_attempts.attempt_id = ep.task_attempt_id - JOIN ( - -- pick exactly one "latest" activity per process, - -- tiebreaking so that running‐states are lower priority - SELECT execution_process_id, status - FROM ( - SELECT - execution_process_id, - status, - ROW_NUMBER() OVER ( - PARTITION BY execution_process_id - ORDER BY - created_at DESC, - CASE - WHEN status IN ('setuprunning','executorrunning') THEN 1 - ELSE 0 - END - ) AS rn - FROM task_attempt_activities - ) sub - WHERE rn = 1 - ) latest_act - ON ep.id = latest_act.execution_process_id - WHERE latest_attempts.rn = 1 -- Only consider the latest attempt - AND latest_act.status IN ('setupfailed','executorfailed') - ) failed_attempts - ON t.id = failed_attempts.task_id - LEFT JOIN ( - SELECT task_id, executor - FROM ( - SELECT task_id, executor, created_at, - ROW_NUMBER() OVER (PARTITION BY task_id ORDER BY created_at DESC) AS rn - FROM task_attempts - ) latest_attempts - WHERE rn = 1 - ) latest_executor_attempts - ON t.id = latest_executor_attempts.task_id - WHERE t.project_id = $1 - ORDER BY t.created_at DESC; - "#, + ON ta.id = ep.task_attempt_id + WHERE ep.process_type IN ('setupscript','codingagent') + ) sub + WHERE sub.rn = 1 + AND sub.status IN ('failed','killed') + ) fa + ON t.id = fa.task_id + + -- get the executor of the latest attempt + LEFT JOIN ( + SELECT task_id, executor + FROM ( + SELECT task_id, executor, created_at, + ROW_NUMBER() OVER (PARTITION BY task_id ORDER BY created_at DESC) AS rn + FROM task_attempts + ) latest_attempts + WHERE rn = 1 + ) latest_executor_attempts + ON t.id = latest_executor_attempts.task_id + + WHERE t.project_id = $1 + ORDER BY t.created_at DESC; + "#, project_id ) .fetch_all(pool) @@ -196,19 +167,19 @@ impl Task { let tasks = records .into_iter() - .map(|record| TaskWithAttemptStatus { - id: record.id, - project_id: record.project_id, - title: record.title, - description: record.description, - status: record.status, - parent_task_attempt: record.parent_task_attempt, - created_at: record.created_at, - updated_at: record.updated_at, - has_in_progress_attempt: record.has_in_progress_attempt != 0, - has_merged_attempt: record.has_merged_attempt != 0, - has_failed_attempt: record.has_failed_attempt != 0, - latest_attempt_executor: record.latest_attempt_executor, + .map(|rec| TaskWithAttemptStatus { + id: rec.id, + project_id: rec.project_id, + title: rec.title, + description: rec.description, + status: rec.status, + parent_task_attempt: rec.parent_task_attempt, + created_at: rec.created_at, + updated_at: rec.updated_at, + has_in_progress_attempt: rec.has_in_progress_attempt != 0, + has_merged_attempt: rec.has_merged_attempt != 0, + has_failed_attempt: rec.has_failed_attempt != 0, + latest_attempt_executor: rec.latest_attempt_executor, }) .collect(); diff --git a/backend/src/models/task_attempt_activity.rs b/backend/src/models/task_attempt_activity.rs deleted file mode 100644 index 8fefd5c1..00000000 --- a/backend/src/models/task_attempt_activity.rs +++ /dev/null @@ -1,136 +0,0 @@ -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use sqlx::{FromRow, SqlitePool}; -use ts_rs::TS; -use uuid::Uuid; - -use super::task_attempt::TaskAttemptStatus; - -#[derive(Debug, Clone, FromRow, Serialize, Deserialize, TS)] -#[ts(export)] -pub struct TaskAttemptActivity { - pub id: Uuid, - pub execution_process_id: Uuid, // Foreign key to ExecutionProcess - pub status: TaskAttemptStatus, - pub note: Option, - pub created_at: DateTime, -} - -#[derive(Debug, Deserialize, TS)] -#[ts(export)] -pub struct CreateTaskAttemptActivity { - pub execution_process_id: Uuid, - pub status: Option, - pub note: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize, TS)] -#[ts(export)] -pub struct TaskAttemptActivityWithPrompt { - pub id: Uuid, - pub execution_process_id: Uuid, - pub status: TaskAttemptStatus, - pub note: Option, - pub created_at: DateTime, - pub prompt: Option, // From executor_session -} - -impl TaskAttemptActivity { - #[allow(dead_code)] - pub async fn find_by_execution_process_id( - pool: &SqlitePool, - execution_process_id: Uuid, - ) -> Result, sqlx::Error> { - sqlx::query_as!( - TaskAttemptActivity, - r#"SELECT id as "id!: Uuid", execution_process_id as "execution_process_id!: Uuid", status as "status!: TaskAttemptStatus", note, created_at as "created_at!: DateTime" - FROM task_attempt_activities - WHERE execution_process_id = $1 - ORDER BY created_at DESC"#, - execution_process_id - ) - .fetch_all(pool) - .await - } - - pub async fn create( - pool: &SqlitePool, - data: &CreateTaskAttemptActivity, - activity_id: Uuid, - status: TaskAttemptStatus, - ) -> Result { - let status_value = status as TaskAttemptStatus; - sqlx::query_as!( - TaskAttemptActivity, - r#"INSERT INTO task_attempt_activities (id, execution_process_id, status, note) - VALUES ($1, $2, $3, $4) - RETURNING id as "id!: Uuid", execution_process_id as "execution_process_id!: Uuid", status as "status!: TaskAttemptStatus", note, created_at as "created_at!: DateTime""#, - activity_id, - data.execution_process_id, - status_value, - data.note - ) - .fetch_one(pool) - .await - } - - #[allow(dead_code)] - pub async fn find_processes_with_latest_running_status( - pool: &SqlitePool, - ) -> Result, sqlx::Error> { - let records = sqlx::query!( - r#"SELECT DISTINCT ep.id as "id!: Uuid" - FROM execution_processes ep - INNER JOIN ( - SELECT execution_process_id, MAX(created_at) as latest_created_at - FROM task_attempt_activities - GROUP BY execution_process_id - ) latest_activity ON ep.id = latest_activity.execution_process_id - INNER JOIN task_attempt_activities taa ON ep.id = taa.execution_process_id - AND taa.created_at = latest_activity.latest_created_at - WHERE taa.status IN ($1, $2)"#, - TaskAttemptStatus::ExecutorRunning as TaskAttemptStatus, - TaskAttemptStatus::SetupRunning as TaskAttemptStatus - ) - .fetch_all(pool) - .await?; - - Ok(records.into_iter().map(|r| r.id).collect()) - } - - /// Find activities for all execution processes in a task attempt, with executor session prompts - pub async fn find_with_prompts_by_task_attempt_id( - pool: &SqlitePool, - task_attempt_id: Uuid, - ) -> Result, sqlx::Error> { - let records = sqlx::query!( - r#"SELECT - taa.id as "activity_id!: Uuid", - taa.execution_process_id as "execution_process_id!: Uuid", - taa.status as "status!: TaskAttemptStatus", - taa.note, - taa.created_at as "created_at!: DateTime", - es.prompt - FROM task_attempt_activities taa - INNER JOIN execution_processes ep ON taa.execution_process_id = ep.id - LEFT JOIN executor_sessions es ON es.execution_process_id = ep.id - WHERE ep.task_attempt_id = $1 - ORDER BY taa.created_at ASC"#, - task_attempt_id - ) - .fetch_all(pool) - .await?; - - Ok(records - .into_iter() - .map(|record| TaskAttemptActivityWithPrompt { - id: record.activity_id, - execution_process_id: record.execution_process_id, - status: record.status, - note: record.note, - created_at: record.created_at, - prompt: record.prompt, - }) - .collect()) - } -} diff --git a/backend/src/routes/task_attempts.rs b/backend/src/routes/task_attempts.rs index e4a212ca..0c95a91d 100644 --- a/backend/src/routes/task_attempts.rs +++ b/backend/src/routes/task_attempts.rs @@ -21,10 +21,7 @@ use crate::{ task::{Task, TaskStatus}, task_attempt::{ BranchStatus, CreateFollowUpAttempt, CreatePrParams, CreateTaskAttempt, TaskAttempt, - TaskAttemptState, TaskAttemptStatus, WorktreeDiff, - }, - task_attempt_activity::{ - CreateTaskAttemptActivity, TaskAttemptActivity, TaskAttemptActivityWithPrompt, + TaskAttemptState, WorktreeDiff, }, ApiResponse, }, @@ -76,40 +73,6 @@ pub async fn get_task_attempts( } } -pub async fn get_task_attempt_activities( - Path((project_id, task_id, attempt_id)): Path<(Uuid, Uuid, Uuid)>, - State(app_state): State, -) -> Result>>, StatusCode> { - // Verify task attempt exists and belongs to the correct task - match TaskAttempt::exists_for_task(&app_state.db_pool, attempt_id, task_id, project_id).await { - Ok(false) => return Err(StatusCode::NOT_FOUND), - Err(e) => { - tracing::error!("Failed to check task attempt existence: {}", e); - return Err(StatusCode::INTERNAL_SERVER_ERROR); - } - Ok(true) => {} - } - - // Get activities with prompts for the task attempt - match TaskAttemptActivity::find_with_prompts_by_task_attempt_id(&app_state.db_pool, attempt_id) - .await - { - Ok(activities) => Ok(ResponseJson(ApiResponse { - success: true, - data: Some(activities), - message: None, - })), - Err(e) => { - tracing::error!( - "Failed to fetch task attempt activities for attempt {}: {}", - attempt_id, - e - ); - Err(StatusCode::INTERNAL_SERVER_ERROR) - } - } -} - pub async fn create_task_attempt( Path((project_id, task_id)): Path<(Uuid, Uuid)>, State(app_state): State, @@ -174,61 +137,6 @@ pub async fn create_task_attempt( } } -pub async fn create_task_attempt_activity( - Path((project_id, task_id, attempt_id)): Path<(Uuid, Uuid, Uuid)>, - State(app_state): State, - Json(payload): Json, -) -> Result>, StatusCode> { - // Verify task attempt exists and belongs to the correct task - match TaskAttempt::exists_for_task(&app_state.db_pool, attempt_id, task_id, project_id).await { - Ok(false) => return Err(StatusCode::NOT_FOUND), - Err(e) => { - tracing::error!("Failed to check task attempt existence: {}", e); - return Err(StatusCode::INTERNAL_SERVER_ERROR); - } - Ok(true) => {} - } - - let id = Uuid::new_v4(); - - // Check that execution_process_id is provided in payload - if payload.execution_process_id == Uuid::nil() { - return Err(StatusCode::BAD_REQUEST); - } - - // Verify the execution process exists and belongs to this task attempt - match ExecutionProcess::find_by_id(&app_state.db_pool, payload.execution_process_id).await { - Ok(Some(process)) => { - if process.task_attempt_id != attempt_id { - return Err(StatusCode::BAD_REQUEST); - } - } - Ok(None) => return Err(StatusCode::NOT_FOUND), - Err(e) => { - tracing::error!("Failed to verify execution process: {}", e); - return Err(StatusCode::INTERNAL_SERVER_ERROR); - } - } - - // Default to SetupRunning status if not provided - let status = payload - .status - .clone() - .unwrap_or(TaskAttemptStatus::SetupRunning); - - match TaskAttemptActivity::create(&app_state.db_pool, &payload, id, status).await { - Ok(activity) => Ok(ResponseJson(ApiResponse { - success: true, - data: Some(activity), - message: Some("Task attempt activity created successfully".to_string()), - })), - Err(e) => { - tracing::error!("Failed to create task attempt activity: {}", e); - Err(StatusCode::INTERNAL_SERVER_ERROR) - } - } -} - pub async fn get_task_attempt_diff( Path((project_id, task_id, attempt_id)): Path<(Uuid, Uuid, Uuid)>, State(app_state): State, @@ -736,36 +644,7 @@ pub async fn stop_all_execution_processes( tracing::error!("Failed to update execution process status: {}", e); errors.push(format!("Failed to update process {} status", process.id)); } else { - // Create activity record for stopped processes (skip dev servers) - if !matches!( - process.process_type, - crate::models::execution_process::ExecutionProcessType::DevServer - ) { - let activity_id = Uuid::new_v4(); - let create_activity = CreateTaskAttemptActivity { - execution_process_id: process.id, - status: Some(TaskAttemptStatus::ExecutorFailed), - note: Some(format!( - "Execution process {:?} ({}) stopped by user", - process.process_type, process.id - )), - }; - - if let Err(e) = TaskAttemptActivity::create( - &app_state.db_pool, - &create_activity, - activity_id, - TaskAttemptStatus::ExecutorFailed, - ) - .await - { - tracing::error!("Failed to create stopped activity: {}", e); - errors.push(format!( - "Failed to create activity for process {}", - process.id - )); - } - } + // Process stopped successfully } } Ok(false) => { @@ -824,7 +703,7 @@ pub async fn stop_execution_process( } // Verify execution process exists and belongs to the task attempt - let process = match ExecutionProcess::find_by_id(&app_state.db_pool, process_id).await { + match ExecutionProcess::find_by_id(&app_state.db_pool, process_id).await { Ok(Some(process)) if process.task_attempt_id == attempt_id => process, Ok(Some(_)) => return Err(StatusCode::NOT_FOUND), // Process exists but wrong attempt Ok(None) => return Err(StatusCode::NOT_FOUND), @@ -864,33 +743,7 @@ pub async fn stop_execution_process( return Err(StatusCode::INTERNAL_SERVER_ERROR); } - // Create activity record for stopped processes (skip dev servers) - if !matches!( - process.process_type, - crate::models::execution_process::ExecutionProcessType::DevServer - ) { - let activity_id = Uuid::new_v4(); - let create_activity = CreateTaskAttemptActivity { - execution_process_id: process_id, - status: Some(TaskAttemptStatus::ExecutorFailed), - note: Some(format!( - "Execution process {:?} ({}) stopped by user", - process.process_type, process_id - )), - }; - - if let Err(e) = TaskAttemptActivity::create( - &app_state.db_pool, - &create_activity, - activity_id, - TaskAttemptStatus::ExecutorFailed, - ) - .await - { - tracing::error!("Failed to create stopped activity: {}", e); - return Err(StatusCode::INTERNAL_SERVER_ERROR); - } - } + // Process stopped successfully Ok(ResponseJson(ApiResponse { success: true, @@ -1556,10 +1409,7 @@ pub fn task_attempts_router() -> Router { "/projects/:project_id/tasks/:task_id/attempts", get(get_task_attempts).post(create_task_attempt), ) - .route( - "/projects/:project_id/tasks/:task_id/attempts/:attempt_id/activities", - get(get_task_attempt_activities).post(create_task_attempt_activity), - ) + .route( "/projects/:project_id/tasks/:task_id/attempts/:attempt_id/diff", diff --git a/backend/src/services/process_service.rs b/backend/src/services/process_service.rs index f5bcafd5..a11512f4 100644 --- a/backend/src/services/process_service.rs +++ b/backend/src/services/process_service.rs @@ -9,8 +9,7 @@ use crate::{ executor_session::{CreateExecutorSession, ExecutorSession}, project::Project, task::Task, - task_attempt::{TaskAttempt, TaskAttemptError, TaskAttemptStatus}, - task_attempt_activity::{CreateTaskAttemptActivity, TaskAttemptActivity}, + task_attempt::{TaskAttempt, TaskAttemptError}, }, utils::shell::get_shell_command, }; @@ -120,14 +119,7 @@ impl ProcessService { ) .await?; - // Create activity record - Self::create_activity_record( - pool, - process_id, - TaskAttemptStatus::SetupRunning, - "Starting setup script with delegation", - ) - .await?; + // Setup script starting with delegation tracing::info!( "Starting setup script with delegation to {} for task attempt {}", @@ -218,7 +210,6 @@ impl ProcessService { task_id, crate::executor::ExecutorType::CodingAgent(executor_config), "Starting executor".to_string(), - TaskAttemptStatus::ExecutorRunning, ExecutionProcessType::CodingAgent, &task_attempt.worktree_path, ) @@ -286,7 +277,6 @@ impl ProcessService { task_id, crate::executor::ExecutorType::DevServer(dev_script), "Starting dev server".to_string(), - TaskAttemptStatus::ExecutorRunning, // Dev servers don't create activities, just use generic status ExecutionProcessType::DevServer, &worktree_path, ) @@ -466,7 +456,6 @@ impl ProcessService { task_id, followup_executor, "Starting follow-up executor".to_string(), - TaskAttemptStatus::ExecutorRunning, ExecutionProcessType::CodingAgent, &worktree_path, ) @@ -492,7 +481,6 @@ impl ProcessService { task_id, new_session_executor, "Starting new executor session (follow-up session failed)".to_string(), - TaskAttemptStatus::ExecutorRunning, ExecutionProcessType::CodingAgent, &worktree_path, ) @@ -514,7 +502,6 @@ impl ProcessService { task_id: Uuid, executor_type: crate::executor::ExecutorType, activity_note: String, - activity_status: TaskAttemptStatus, process_type: ExecutionProcessType, worktree_path: &str, ) -> Result<(), TaskAttemptError> { @@ -550,11 +537,7 @@ impl ProcessService { .await?; } - // Create activity record (skip for dev servers as they run in parallel) - if !matches!(process_type, ExecutionProcessType::DevServer) { - Self::create_activity_record(pool, process_id, activity_status.clone(), &activity_note) - .await?; - } + // Process started successfully tracing::info!("Starting {} for task attempt {}", activity_note, attempt_id); @@ -625,7 +608,6 @@ impl ProcessService { task_id, crate::executor::ExecutorType::SetupScript(setup_script.clone()), "Starting setup script".to_string(), - TaskAttemptStatus::SetupRunning, ExecutionProcessType::SetupScript, worktree_path, ) @@ -721,26 +703,6 @@ impl ProcessService { .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> { - let activity_id = Uuid::new_v4(); - let create_activity = CreateTaskAttemptActivity { - execution_process_id: process_id, - status: Some(activity_status.clone()), - note: Some(activity_note.to_string()), - }; - - TaskAttemptActivity::create(pool, &create_activity, activity_id, activity_status) - .await - .map(|_| ()) - .map_err(TaskAttemptError::from) - } - /// Execute the process based on type async fn execute_process( executor_type: &crate::executor::ExecutorType, diff --git a/frontend/src/components/context/TaskDetailsContextProvider.tsx b/frontend/src/components/context/TaskDetailsContextProvider.tsx index 3a9550ce..303f370a 100644 --- a/frontend/src/components/context/TaskDetailsContextProvider.tsx +++ b/frontend/src/components/context/TaskDetailsContextProvider.tsx @@ -79,7 +79,6 @@ const TaskDetailsProvider: FC<{ ); const [attemptData, setAttemptData] = useState({ - activities: [], processes: [], runningProcessDetails: {}, }); @@ -234,29 +233,28 @@ const TaskDetailsProvider: FC<{ if (!task) return; try { - const [activitiesResult, processesResult] = await Promise.all([ - attemptsApi.getActivities(projectId, taskId, attemptId), - attemptsApi.getExecutionProcesses(projectId, taskId, attemptId), - ]); + const processesResult = await attemptsApi.getExecutionProcesses( + projectId, + taskId, + attemptId + ); - if (activitiesResult !== undefined && processesResult !== undefined) { - const runningActivities = activitiesResult.filter( - (activity) => - activity.status === 'setuprunning' || - activity.status === 'executorrunning' + if (processesResult !== undefined) { + const runningProcesses = processesResult.filter( + (process) => process.status === 'running' ); const runningProcessDetails: Record = {}; - // Fetch details for running activities - for (const activity of runningActivities) { + // Fetch details for running processes + for (const process of runningProcesses) { const result = await executionProcessesApi.getDetails( projectId, - activity.execution_process_id + process.id ); if (result !== undefined) { - runningProcessDetails[activity.execution_process_id] = result; + runningProcessDetails[process.id] = result; } } @@ -277,7 +275,6 @@ const TaskDetailsProvider: FC<{ setAttemptData((prev: AttemptData) => { const newData = { - activities: activitiesResult, processes: processesResult, runningProcessDetails, }; diff --git a/frontend/src/components/tasks/TaskActivityHistory.tsx b/frontend/src/components/tasks/TaskActivityHistory.tsx deleted file mode 100644 index c74dc99a..00000000 --- a/frontend/src/components/tasks/TaskActivityHistory.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import { useState } from 'react'; -import { ChevronDown, ChevronUp, Clock, Code } from 'lucide-react'; -import { Button } from '@/components/ui/button'; -import { Label } from '@/components/ui/label'; -import { Chip } from '@/components/ui/chip'; -import { NormalizedConversationViewer } from './TaskDetails/LogsTab/NormalizedConversationViewer.tsx'; -import type { - ExecutionProcess, - TaskAttempt, - TaskAttemptActivityWithPrompt, - TaskAttemptStatus, -} from 'shared/types'; - -interface TaskActivityHistoryProps { - selectedAttempt: TaskAttempt | null; - activities: TaskAttemptActivityWithPrompt[]; - runningProcessDetails: Record; -} - -const getAttemptStatusDisplay = ( - status: TaskAttemptStatus -): { label: string; dotColor: string } => { - switch (status) { - case 'setuprunning': - return { - label: 'Setup Running', - dotColor: 'bg-blue-500', - }; - case 'setupcomplete': - return { - label: 'Setup Complete', - dotColor: 'bg-green-500', - }; - case 'setupfailed': - return { - label: 'Setup Failed', - dotColor: 'bg-red-500', - }; - case 'executorrunning': - return { - label: 'Executor Running', - dotColor: 'bg-blue-500', - }; - case 'executorcomplete': - return { - label: 'Executor Complete', - dotColor: 'bg-green-500', - }; - case 'executorfailed': - return { - label: 'Executor Failed', - dotColor: 'bg-red-500', - }; - default: - return { - label: 'Unknown', - dotColor: 'bg-gray-400', - }; - } -}; - -export function TaskActivityHistory({ - selectedAttempt, - activities, - runningProcessDetails, -}: TaskActivityHistoryProps) { - const [expandedOutputs, setExpandedOutputs] = useState>( - new Set() - ); - - const toggleOutputExpansion = (processId: string) => { - setExpandedOutputs((prev) => { - const newSet = new Set(prev); - if (newSet.has(processId)) { - newSet.delete(processId); - } else { - newSet.add(processId); - } - return newSet; - }); - }; - - if (!selectedAttempt) { - return null; - } - - return ( -
- - {activities.length === 0 ? ( -
- No activities found -
- ) : ( -
- {/* Fake worktree created activity */} -
-
- New Worktree - - {selectedAttempt.worktree_path} - -
- - {new Date(selectedAttempt.created_at).toLocaleTimeString([], { - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - })} -
-
-
- {activities.slice().map((activity) => ( -
- {/* Compact activity message */} -
- - {getAttemptStatusDisplay(activity.status).label} - - {activity.note && ( - - {activity.note} - - )} -
- - {new Date(activity.created_at).toLocaleTimeString([], { - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - })} -
-
- - {/* Show prompt for coding agent executions */} - {activity.prompt && activity.status === 'executorrunning' && ( -
-
-
- - - Prompt - -
-
-                      {activity.prompt}
-                    
-
-
- )} - - {/* Show stdio output for running processes */} - {(activity.status === 'setuprunning' || - activity.status === 'executorrunning') && - runningProcessDetails[activity.execution_process_id] && ( -
-
- -
- -
- )} -
- ))} -
- )} -
- ); -} diff --git a/frontend/src/components/tasks/TaskDetails/LogsTab/Conversation.tsx b/frontend/src/components/tasks/TaskDetails/LogsTab/Conversation.tsx index ce898b95..06802e04 100644 --- a/frontend/src/components/tasks/TaskDetails/LogsTab/Conversation.tsx +++ b/frontend/src/components/tasks/TaskDetails/LogsTab/Conversation.tsx @@ -27,12 +27,7 @@ function Conversation() { scrollContainerRef.current.scrollTop = scrollContainerRef.current.scrollHeight; } - }, [ - attemptData.activities, - attemptData.processes, - conversationUpdateTrigger, - shouldAutoScrollLogs, - ]); + }, [attemptData.processes, conversationUpdateTrigger, shouldAutoScrollLogs]); const handleLogsScroll = useCallback(() => { if (scrollContainerRef.current) { diff --git a/frontend/src/components/tasks/TaskDetailsToolbar.tsx b/frontend/src/components/tasks/TaskDetailsToolbar.tsx index 89d71e25..11274bfe 100644 --- a/frontend/src/components/tasks/TaskDetailsToolbar.tsx +++ b/frontend/src/components/tasks/TaskDetailsToolbar.tsx @@ -171,7 +171,6 @@ function TaskDetailsToolbar() { } else { setSelectedAttempt(null); setAttemptData({ - activities: [], processes: [], runningProcessDetails: {}, }); diff --git a/frontend/src/components/tasks/TaskFollowUpSection.tsx b/frontend/src/components/tasks/TaskFollowUpSection.tsx index 5620f44e..1e3d3e56 100644 --- a/frontend/src/components/tasks/TaskFollowUpSection.tsx +++ b/frontend/src/components/tasks/TaskFollowUpSection.tsx @@ -25,21 +25,22 @@ export function TaskFollowUpSection() { const canSendFollowUp = useMemo(() => { if ( !selectedAttempt || - attemptData.activities.length === 0 || + attemptData.processes.length === 0 || isAttemptRunning || isSendingFollowUp ) { return false; } - const codingAgentActivities = attemptData.activities.filter( - (activity) => activity.status === 'executorcomplete' + const completedCodingAgentProcesses = attemptData.processes.filter( + (process) => + process.process_type === 'codingagent' && process.status === 'completed' ); - return codingAgentActivities.length > 0; + return completedCodingAgentProcesses.length > 0; }, [ selectedAttempt, - attemptData.activities, + attemptData.processes, isAttemptRunning, isSendingFollowUp, ]); diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 9645a200..165b389e 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -19,7 +19,6 @@ import { ProjectWithBranch, Task, TaskAttempt, - TaskAttemptActivityWithPrompt, TaskAttemptState, TaskTemplate, TaskWithAttemptStatus, @@ -318,17 +317,6 @@ export const attemptsApi = { return handleApiResponse(response); }, - getActivities: async ( - projectId: string, - taskId: string, - attemptId: string - ): Promise => { - const response = await makeRequest( - `/api/projects/${projectId}/tasks/${taskId}/attempts/${attemptId}/activities` - ); - return handleApiResponse(response); - }, - getDiff: async ( projectId: string, taskId: string, diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index 8cb9456b..e0a4d22a 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -2,11 +2,9 @@ import { DiffChunkType, ExecutionProcess, ExecutionProcessSummary, - TaskAttemptActivityWithPrompt, } from 'shared/types.ts'; export type AttemptData = { - activities: TaskAttemptActivityWithPrompt[]; processes: ExecutionProcessSummary[]; runningProcessDetails: Record; }; diff --git a/shared/types.ts b/shared/types.ts index 01708bff..c4d5dbc7 100644 --- a/shared/types.ts +++ b/shared/types.ts @@ -70,12 +70,6 @@ export type UpdateTaskAttempt = Record; export type CreateFollowUpAttempt = { prompt: string, }; -export type TaskAttemptActivity = { id: string, execution_process_id: string, status: TaskAttemptStatus, note: string | null, created_at: string, }; - -export type TaskAttemptActivityWithPrompt = { id: string, execution_process_id: string, status: TaskAttemptStatus, note: string | null, created_at: string, prompt: string | null, }; - -export type CreateTaskAttemptActivity = { execution_process_id: string, status: TaskAttemptStatus | null, note: string | null, }; - export type DirectoryEntry = { name: string, path: string, is_directory: boolean, is_git_repo: boolean, }; export type DirectoryListResponse = { entries: Array, current_path: string, };