Claude WIP
This commit is contained in:
@@ -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<dyn Executor> {
|
||||
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",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
56
backend/src/executors/claude.rs
Normal file
56
backend/src/executors/claude.rs
Normal file
@@ -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<Child, ExecutorError> {
|
||||
// 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"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod echo;
|
||||
pub mod claude;
|
||||
|
||||
pub use echo::EchoExecutor;
|
||||
pub use claude::ClaudeExecutor;
|
||||
|
||||
@@ -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<TaskAttempt | null>(null)
|
||||
const [attemptActivities, setAttemptActivities] = useState<TaskAttemptActivity[]>([])
|
||||
const [activitiesLoading, setActivitiesLoading] = useState(false)
|
||||
const [selectedExecutor, setSelectedExecutor] = useState<ExecutorConfig>({ 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
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<h3 className="text-lg font-semibold">Task Attempts</h3>
|
||||
<Button
|
||||
onClick={createNewAttempt}
|
||||
disabled={creatingAttempt}
|
||||
size="sm"
|
||||
>
|
||||
{creatingAttempt ? 'Creating...' : 'Create New Attempt'}
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-sm">Executor:</Label>
|
||||
<Select
|
||||
value={selectedExecutor.type}
|
||||
onValueChange={(value) => setSelectedExecutor({ type: value as "echo" | "claude" })}
|
||||
>
|
||||
<SelectTrigger className="w-32">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="echo">Echo</SelectItem>
|
||||
<SelectItem value="claude">Claude</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<Button
|
||||
onClick={createNewAttempt}
|
||||
disabled={creatingAttempt}
|
||||
size="sm"
|
||||
>
|
||||
{creatingAttempt ? 'Creating...' : 'Create New Attempt'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{taskAttemptsLoading ? (
|
||||
<div className="text-center py-4">Loading attempts...</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
export type ApiResponse<T> = { 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, };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user