Add amp support
This commit is contained in:
@@ -5,7 +5,7 @@ use tokio::process::Child;
|
|||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::executors::{ClaudeExecutor, EchoExecutor};
|
use crate::executors::{AmpExecutor, ClaudeExecutor, EchoExecutor};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ExecutorError {
|
pub enum ExecutorError {
|
||||||
@@ -146,6 +146,7 @@ pub trait Executor: Send + Sync {
|
|||||||
pub enum ExecutorConfig {
|
pub enum ExecutorConfig {
|
||||||
Echo,
|
Echo,
|
||||||
Claude,
|
Claude,
|
||||||
|
Amp,
|
||||||
// Future executors can be added here
|
// Future executors can be added here
|
||||||
// Shell { command: String },
|
// Shell { command: String },
|
||||||
// Docker { image: String, command: String },
|
// Docker { image: String, command: String },
|
||||||
@@ -156,13 +157,7 @@ impl ExecutorConfig {
|
|||||||
match self {
|
match self {
|
||||||
ExecutorConfig::Echo => Box::new(EchoExecutor),
|
ExecutorConfig::Echo => Box::new(EchoExecutor),
|
||||||
ExecutorConfig::Claude => Box::new(ClaudeExecutor),
|
ExecutorConfig::Claude => Box::new(ClaudeExecutor),
|
||||||
}
|
ExecutorConfig::Amp => Box::new(AmpExecutor),
|
||||||
}
|
|
||||||
|
|
||||||
pub fn executor_type(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
ExecutorConfig::Echo => "echo",
|
|
||||||
ExecutorConfig::Claude => "claude",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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!(
|
let prompt = format!(
|
||||||
"Task title: {}
|
"Task title: {}
|
||||||
Task description:{}",
|
Task description: {}",
|
||||||
task.title,
|
task.title,
|
||||||
task.description
|
task.description
|
||||||
.as_deref()
|
.as_deref()
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
pub mod amp;
|
||||||
pub mod claude;
|
pub mod claude;
|
||||||
pub mod echo;
|
pub mod echo;
|
||||||
|
|
||||||
|
pub use amp::AmpExecutor;
|
||||||
pub use claude::ClaudeExecutor;
|
pub use claude::ClaudeExecutor;
|
||||||
pub use echo::EchoExecutor;
|
pub use echo::EchoExecutor;
|
||||||
|
|||||||
@@ -225,6 +225,7 @@ impl TaskAttempt {
|
|||||||
match executor_name.as_str() {
|
match executor_name.as_str() {
|
||||||
"echo" => ExecutorConfig::Echo.create_executor(),
|
"echo" => ExecutorConfig::Echo.create_executor(),
|
||||||
"claude" => ExecutorConfig::Claude.create_executor(),
|
"claude" => ExecutorConfig::Claude.create_executor(),
|
||||||
|
"amp" => ExecutorConfig::Amp.create_executor(),
|
||||||
_ => ExecutorConfig::Echo.create_executor(), // Default fallback
|
_ => ExecutorConfig::Echo.create_executor(), // Default fallback
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -85,7 +85,9 @@ export function TaskDetailsDialog({
|
|||||||
const [savingTask, setSavingTask] = useState(false);
|
const [savingTask, setSavingTask] = useState(false);
|
||||||
|
|
||||||
// Check if the selected attempt is currently running (latest activity is "inprogress")
|
// 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";
|
attemptActivities[0].status === "inprogress";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -94,7 +96,7 @@ export function TaskDetailsDialog({
|
|||||||
setSelectedAttempt(null);
|
setSelectedAttempt(null);
|
||||||
setAttemptActivities([]);
|
setAttemptActivities([]);
|
||||||
setActivitiesLoading(false);
|
setActivitiesLoading(false);
|
||||||
|
|
||||||
fetchTaskAttempts(task.id);
|
fetchTaskAttempts(task.id);
|
||||||
// Initialize edit state with current task values
|
// Initialize edit state with current task values
|
||||||
setEditedTitle(task.title);
|
setEditedTitle(task.title);
|
||||||
@@ -572,15 +574,18 @@ export function TaskDetailsDialog({
|
|||||||
<Select
|
<Select
|
||||||
value={selectedExecutor}
|
value={selectedExecutor}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
setSelectedExecutor(value as "echo" | "claude")
|
setSelectedExecutor(
|
||||||
|
value as "echo" | "claude" | "amp"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="echo">Echo</SelectItem>
|
|
||||||
<SelectItem value="claude">Claude</SelectItem>
|
<SelectItem value="claude">Claude</SelectItem>
|
||||||
|
<SelectItem value="amp">Amp</SelectItem>
|
||||||
|
<SelectItem value="echo">Echo</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Button
|
<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;
|
if (!task || !projectId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -193,7 +196,7 @@ export function TaskDetailsPage() {
|
|||||||
if (!isBackgroundUpdate) {
|
if (!isBackgroundUpdate) {
|
||||||
setActivitiesLoading(true);
|
setActivitiesLoading(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await makeRequest(
|
const response = await makeRequest(
|
||||||
`/api/projects/${projectId}/tasks/${task.id}/attempts/${attemptId}/activities`
|
`/api/projects/${projectId}/tasks/${task.id}/attempts/${attemptId}/activities`
|
||||||
);
|
);
|
||||||
@@ -221,9 +224,11 @@ export function TaskDetailsPage() {
|
|||||||
fetchAttemptActivities(attempt.id);
|
fetchAttemptActivities(attempt.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleUpdateTaskFromDialog = async (
|
||||||
|
title: string,
|
||||||
const handleUpdateTaskFromDialog = async (title: string, description: string, status: TaskStatus) => {
|
description: string,
|
||||||
|
status: TaskStatus
|
||||||
|
) => {
|
||||||
if (!task || !projectId) return;
|
if (!task || !projectId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -588,7 +593,11 @@ export function TaskDetailsPage() {
|
|||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{selectedAttempt && (
|
{selectedAttempt && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => navigate(`/projects/${projectId}/tasks/${taskId}/attempts/${selectedAttempt.id}/compare`)}
|
onClick={() =>
|
||||||
|
navigate(
|
||||||
|
`/projects/${projectId}/tasks/${taskId}/attempts/${selectedAttempt.id}/compare`
|
||||||
|
)
|
||||||
|
}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
@@ -615,15 +624,18 @@ export function TaskDetailsPage() {
|
|||||||
<Select
|
<Select
|
||||||
value={selectedExecutor}
|
value={selectedExecutor}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
setSelectedExecutor(value as "echo" | "claude")
|
setSelectedExecutor(
|
||||||
|
value as "echo" | "claude" | "amp"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="echo">Echo</SelectItem>
|
|
||||||
<SelectItem value="claude">Claude</SelectItem>
|
<SelectItem value="claude">Claude</SelectItem>
|
||||||
|
<SelectItem value="amp">Amp</SelectItem>
|
||||||
|
<SelectItem value="echo">Echo</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
export type ApiResponse<T> = { success: boolean, data: T | null, message: string | null, };
|
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, };
|
export type CreateProject = { name: string, git_repo_path: string, use_existing_repo: boolean, };
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user