Add amp support
This commit is contained in:
@@ -5,7 +5,7 @@ use tokio::process::Child;
|
||||
use ts_rs::TS;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::executors::{ClaudeExecutor, EchoExecutor};
|
||||
use crate::executors::{AmpExecutor, ClaudeExecutor, EchoExecutor};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ExecutorError {
|
||||
@@ -146,6 +146,7 @@ pub trait Executor: Send + Sync {
|
||||
pub enum ExecutorConfig {
|
||||
Echo,
|
||||
Claude,
|
||||
Amp,
|
||||
// Future executors can be added here
|
||||
// Shell { command: String },
|
||||
// Docker { image: String, command: String },
|
||||
@@ -156,13 +157,7 @@ impl ExecutorConfig {
|
||||
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",
|
||||
ExecutorConfig::Amp => Box::new(AmpExecutor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
62
backend/src/executors/amp.rs
Normal file
62
backend/src/executors/amp.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
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 AmpExecutor;
|
||||
|
||||
#[async_trait]
|
||||
impl Executor for AmpExecutor {
|
||||
fn executor_type(&self) -> &'static str {
|
||||
"amp"
|
||||
}
|
||||
|
||||
async fn spawn(
|
||||
&self,
|
||||
pool: &sqlx::SqlitePool,
|
||||
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)?;
|
||||
|
||||
use std::process::Stdio;
|
||||
use tokio::{io::AsyncWriteExt, process::Command};
|
||||
|
||||
let prompt = format!(
|
||||
"Task title: {}\nTask description: {}",
|
||||
task.title,
|
||||
task.description
|
||||
.as_deref()
|
||||
.unwrap_or("No description provided")
|
||||
);
|
||||
|
||||
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("--format=jsonl")
|
||||
.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(prompt.as_bytes()).await.unwrap();
|
||||
stdin.shutdown().await.unwrap(); // or `drop(stdin);`
|
||||
}
|
||||
|
||||
Ok(child)
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Executes tasks using Claude CLI for AI-powered code assistance"
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ impl Executor for ClaudeExecutor {
|
||||
|
||||
let prompt = format!(
|
||||
"Task title: {}
|
||||
Task description:{}",
|
||||
Task description: {}",
|
||||
task.title,
|
||||
task.description
|
||||
.as_deref()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod amp;
|
||||
pub mod claude;
|
||||
pub mod echo;
|
||||
|
||||
pub use amp::AmpExecutor;
|
||||
pub use claude::ClaudeExecutor;
|
||||
pub use echo::EchoExecutor;
|
||||
|
||||
@@ -225,6 +225,7 @@ impl TaskAttempt {
|
||||
match executor_name.as_str() {
|
||||
"echo" => ExecutorConfig::Echo.create_executor(),
|
||||
"claude" => ExecutorConfig::Claude.create_executor(),
|
||||
"amp" => ExecutorConfig::Amp.create_executor(),
|
||||
_ => ExecutorConfig::Echo.create_executor(), // Default fallback
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -85,7 +85,9 @@ export function TaskDetailsDialog({
|
||||
const [savingTask, setSavingTask] = useState(false);
|
||||
|
||||
// Check if the selected attempt is currently running (latest activity is "inprogress")
|
||||
const isAttemptRunning = selectedAttempt && attemptActivities.length > 0 &&
|
||||
const isAttemptRunning =
|
||||
selectedAttempt &&
|
||||
attemptActivities.length > 0 &&
|
||||
attemptActivities[0].status === "inprogress";
|
||||
|
||||
useEffect(() => {
|
||||
@@ -572,15 +574,18 @@ export function TaskDetailsDialog({
|
||||
<Select
|
||||
value={selectedExecutor}
|
||||
onValueChange={(value) =>
|
||||
setSelectedExecutor(value as "echo" | "claude")
|
||||
setSelectedExecutor(
|
||||
value as "echo" | "claude" | "amp"
|
||||
)
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="echo">Echo</SelectItem>
|
||||
<SelectItem value="claude">Claude</SelectItem>
|
||||
<SelectItem value="amp">Amp</SelectItem>
|
||||
<SelectItem value="echo">Echo</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
|
||||
@@ -185,7 +185,10 @@ export function TaskDetailsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchAttemptActivities = async (attemptId: string, isBackgroundUpdate = false) => {
|
||||
const fetchAttemptActivities = async (
|
||||
attemptId: string,
|
||||
isBackgroundUpdate = false
|
||||
) => {
|
||||
if (!task || !projectId) return;
|
||||
|
||||
try {
|
||||
@@ -221,9 +224,11 @@ export function TaskDetailsPage() {
|
||||
fetchAttemptActivities(attempt.id);
|
||||
};
|
||||
|
||||
|
||||
|
||||
const handleUpdateTaskFromDialog = async (title: string, description: string, status: TaskStatus) => {
|
||||
const handleUpdateTaskFromDialog = async (
|
||||
title: string,
|
||||
description: string,
|
||||
status: TaskStatus
|
||||
) => {
|
||||
if (!task || !projectId) return;
|
||||
|
||||
try {
|
||||
@@ -588,7 +593,11 @@ export function TaskDetailsPage() {
|
||||
<div className="flex flex-col gap-2">
|
||||
{selectedAttempt && (
|
||||
<Button
|
||||
onClick={() => navigate(`/projects/${projectId}/tasks/${taskId}/attempts/${selectedAttempt.id}/compare`)}
|
||||
onClick={() =>
|
||||
navigate(
|
||||
`/projects/${projectId}/tasks/${taskId}/attempts/${selectedAttempt.id}/compare`
|
||||
)
|
||||
}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
@@ -615,15 +624,18 @@ export function TaskDetailsPage() {
|
||||
<Select
|
||||
value={selectedExecutor}
|
||||
onValueChange={(value) =>
|
||||
setSelectedExecutor(value as "echo" | "claude")
|
||||
setSelectedExecutor(
|
||||
value as "echo" | "claude" | "amp"
|
||||
)
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="echo">Echo</SelectItem>
|
||||
<SelectItem value="claude">Claude</SelectItem>
|
||||
<SelectItem value="amp">Amp</SelectItem>
|
||||
<SelectItem value="echo">Echo</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
export type ApiResponse<T> = { success: boolean, data: T | null, message: string | null, };
|
||||
|
||||
export type ExecutorConfig = { "type": "echo" } | { "type": "claude" };
|
||||
export type ExecutorConfig = { "type": "echo" } | { "type": "claude" } | { "type": "amp" };
|
||||
|
||||
export type CreateProject = { name: string, git_repo_path: string, use_existing_repo: boolean, };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user