Add amp support

This commit is contained in:
Louis Knight-Webb
2025-06-18 00:17:06 -04:00
parent 3ad567cd1c
commit 635ef346df
8 changed files with 99 additions and 22 deletions

View File

@@ -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),
}
}
}

View 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"
}
}

View File

@@ -27,7 +27,7 @@ impl Executor for ClaudeExecutor {
let prompt = format!(
"Task title: {}
Task description:{}",
Task description: {}",
task.title,
task.description
.as_deref()

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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(() => {
@@ -94,7 +96,7 @@ export function TaskDetailsDialog({
setSelectedAttempt(null);
setAttemptActivities([]);
setActivitiesLoading(false);
fetchTaskAttempts(task.id);
// Initialize edit state with current task values
setEditedTitle(task.title);
@@ -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

View File

@@ -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 {
@@ -193,7 +196,7 @@ export function TaskDetailsPage() {
if (!isBackgroundUpdate) {
setActivitiesLoading(true);
}
const response = await makeRequest(
`/api/projects/${projectId}/tasks/${task.id}/attempts/${attemptId}/activities`
);
@@ -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

View File

@@ -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, };