diff --git a/backend/src/executor.rs b/backend/src/executor.rs index 1b8e5883..3ea700a5 100644 --- a/backend/src/executor.rs +++ b/backend/src/executor.rs @@ -5,7 +5,7 @@ use tokio::io::{AsyncBufReadExt, BufReader}; use ts_rs::TS; use uuid::Uuid; -use crate::executors::EchoExecutor; +use crate::executors::{EchoExecutor, ClaudeExecutor}; #[derive(Debug)] pub enum ExecutorError { @@ -124,6 +124,7 @@ pub trait Executor: Send + Sync { #[ts(export)] pub enum ExecutorConfig { Echo, + Claude, // Future executors can be added here // Shell { command: String }, // Docker { image: String, command: String }, @@ -133,12 +134,14 @@ impl ExecutorConfig { pub fn create_executor(&self) -> Box { match self { ExecutorConfig::Echo => Box::new(EchoExecutor), + ExecutorConfig::Claude => Box::new(ClaudeExecutor), } } pub fn executor_type(&self) -> &'static str { match self { ExecutorConfig::Echo => "echo", + ExecutorConfig::Claude => "claude", } } } diff --git a/backend/src/executors/claude.rs b/backend/src/executors/claude.rs new file mode 100644 index 00000000..d91ef841 --- /dev/null +++ b/backend/src/executors/claude.rs @@ -0,0 +1,56 @@ +use async_trait::async_trait; +use tokio::process::{Child, Command}; +use uuid::Uuid; + +use crate::executor::{Executor, ExecutorError}; +use crate::models::task::Task; + +/// An executor that uses Claude CLI to process tasks +pub struct ClaudeExecutor; + +#[async_trait] +impl Executor for ClaudeExecutor { + fn executor_type(&self) -> &'static str { + "claude" + } + + async fn spawn( + &self, + pool: &sqlx::PgPool, + task_id: Uuid, + worktree_path: &str, + ) -> Result { + // Get the task to fetch its description + let task = Task::find_by_id(pool, task_id) + .await? + .ok_or(ExecutorError::TaskNotFound)?; + + let prompt = format!( + "Task: {}\n\nDescription: {}\n\nWorking directory: {}", + task.title, + task.description + .as_deref() + .unwrap_or("No description provided"), + worktree_path + ); + + // Use Claude CLI to process the task + let child = Command::new("claude") + .kill_on_drop(true) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .current_dir(worktree_path) + // .arg("--no-color") + // .arg("--") + // .arg(&prompt) + .arg("--help") + .spawn() + .map_err(ExecutorError::SpawnFailed)?; + + Ok(child) + } + + fn description(&self) -> &'static str { + "Executes tasks using Claude CLI for AI-powered code assistance" + } +} diff --git a/backend/src/executors/mod.rs b/backend/src/executors/mod.rs index e937bc7c..dddcf8e1 100644 --- a/backend/src/executors/mod.rs +++ b/backend/src/executors/mod.rs @@ -1,3 +1,5 @@ pub mod echo; +pub mod claude; pub use echo::EchoExecutor; +pub use claude::ClaudeExecutor; diff --git a/frontend/src/components/tasks/TaskDetailsDialog.tsx b/frontend/src/components/tasks/TaskDetailsDialog.tsx index 04a0fdd4..10f8d29e 100644 --- a/frontend/src/components/tasks/TaskDetailsDialog.tsx +++ b/frontend/src/components/tasks/TaskDetailsDialog.tsx @@ -8,6 +8,7 @@ import { } from '@/components/ui/dialog' import { Label } from '@/components/ui/label' import { Button } from '@/components/ui/button' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { makeAuthenticatedRequest } from '@/lib/auth' import type { TaskStatus, TaskAttempt, TaskAttemptActivity, ExecutorConfig } from 'shared/types' @@ -49,6 +50,7 @@ export function TaskDetailsDialog({ isOpen, onOpenChange, task, projectId, onErr const [selectedAttempt, setSelectedAttempt] = useState(null) const [attemptActivities, setAttemptActivities] = useState([]) const [activitiesLoading, setActivitiesLoading] = useState(false) + const [selectedExecutor, setSelectedExecutor] = useState({ type: "echo" }) const [creatingAttempt, setCreatingAttempt] = useState(false) useEffect(() => { @@ -123,7 +125,7 @@ export function TaskDetailsDialog({ isOpen, onOpenChange, task, projectId, onErr worktree_path: worktreePath, base_commit: null, merge_commit: null, - executor_config: { type: "echo" } as ExecutorConfig, + executor_config: selectedExecutor, }), } ) @@ -175,13 +177,30 @@ export function TaskDetailsDialog({ isOpen, onOpenChange, task, projectId, onErr

Task Attempts

- +
+
+ + +
+ +
{taskAttemptsLoading ? (
Loading attempts...
diff --git a/shared/types.ts b/shared/types.ts index 5fe3cc47..695dbe65 100644 --- a/shared/types.ts +++ b/shared/types.ts @@ -3,7 +3,7 @@ export type ApiResponse = { success: boolean, data: T | null, message: string | null, }; -export type ExecutorConfig = { "type": "echo" }; +export type ExecutorConfig = { "type": "echo" } | { "type": "claude" }; export type CreateProject = { name: string, git_repo_path: string, use_existing_repo: boolean, };