Task attempt 2c218bb9-0865-4b9e-8ecb-0d60ed20ca19 - Final changes
This commit is contained in:
@@ -8,7 +8,8 @@ export const EXECUTOR_TYPES: string[] = [
|
||||
"echo",
|
||||
"claude",
|
||||
"amp",
|
||||
"gemini"
|
||||
"gemini",
|
||||
"opencode"
|
||||
];
|
||||
|
||||
export const EDITOR_TYPES: EditorType[] = [
|
||||
@@ -24,7 +25,8 @@ export const EXECUTOR_LABELS: Record<string, string> = {
|
||||
"echo": "Echo (Test Mode)",
|
||||
"claude": "Claude",
|
||||
"amp": "Amp",
|
||||
"gemini": "Gemini"
|
||||
"gemini": "Gemini",
|
||||
"opencode": "OpenCode"
|
||||
};
|
||||
|
||||
export const EDITOR_LABELS: Record<string, string> = {
|
||||
|
||||
@@ -4,7 +4,7 @@ use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
use ts_rs::TS;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::executors::{AmpExecutor, ClaudeExecutor, EchoExecutor, GeminiExecutor};
|
||||
use crate::executors::{AmpExecutor, ClaudeExecutor, EchoExecutor, GeminiExecutor, OpencodeExecutor};
|
||||
|
||||
/// Context information for spawn failures to provide comprehensive error details
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -231,6 +231,7 @@ pub enum ExecutorConfig {
|
||||
Claude,
|
||||
Amp,
|
||||
Gemini,
|
||||
Opencode,
|
||||
// Future executors can be added here
|
||||
// Shell { command: String },
|
||||
// Docker { image: String, command: String },
|
||||
@@ -251,6 +252,7 @@ impl ExecutorConfig {
|
||||
ExecutorConfig::Claude => Box::new(ClaudeExecutor),
|
||||
ExecutorConfig::Amp => Box::new(AmpExecutor),
|
||||
ExecutorConfig::Gemini => Box::new(GeminiExecutor),
|
||||
ExecutorConfig::Opencode => Box::new(OpencodeExecutor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ pub mod claude;
|
||||
pub mod dev_server;
|
||||
pub mod echo;
|
||||
pub mod gemini;
|
||||
pub mod opencode;
|
||||
pub mod setup_script;
|
||||
|
||||
pub use amp::{AmpExecutor, AmpFollowupExecutor};
|
||||
@@ -10,4 +11,5 @@ pub use claude::{ClaudeExecutor, ClaudeFollowupExecutor};
|
||||
pub use dev_server::DevServerExecutor;
|
||||
pub use echo::EchoExecutor;
|
||||
pub use gemini::{GeminiExecutor, GeminiFollowupExecutor};
|
||||
pub use opencode::{OpencodeExecutor, OpencodeFollowupExecutor};
|
||||
pub use setup_script::SetupScriptExecutor;
|
||||
|
||||
109
backend/src/executors/opencode.rs
Normal file
109
backend/src/executors/opencode.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
use async_trait::async_trait;
|
||||
use command_group::{AsyncCommandGroup, AsyncGroupChild};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
executor::{Executor, ExecutorError},
|
||||
models::task::Task,
|
||||
utils::shell::get_shell_command,
|
||||
};
|
||||
|
||||
/// An executor that uses OpenCode to process tasks
|
||||
pub struct OpencodeExecutor;
|
||||
|
||||
/// An executor that continues an OpenCode thread
|
||||
pub struct OpencodeFollowupExecutor {
|
||||
pub session_id: String,
|
||||
pub prompt: String,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Executor for OpencodeExecutor {
|
||||
async fn spawn(
|
||||
&self,
|
||||
pool: &sqlx::SqlitePool,
|
||||
task_id: Uuid,
|
||||
worktree_path: &str,
|
||||
) -> Result<AsyncGroupChild, 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::process::Command;
|
||||
|
||||
let prompt = format!(
|
||||
"Task title: {}\nTask description: {}",
|
||||
task.title,
|
||||
task.description
|
||||
.as_deref()
|
||||
.unwrap_or("No description provided")
|
||||
);
|
||||
|
||||
// Use shell command for cross-platform compatibility
|
||||
let (shell_cmd, shell_arg) = get_shell_command();
|
||||
let opencode_command = format!("opencode -p \"{}\"", prompt.replace('"', "\\\""));
|
||||
|
||||
let mut command = Command::new(shell_cmd);
|
||||
command
|
||||
.kill_on_drop(true)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.current_dir(worktree_path)
|
||||
.arg(shell_arg)
|
||||
.arg(opencode_command);
|
||||
|
||||
let child = command
|
||||
.group_spawn() // Create new process group so we can kill entire tree
|
||||
.map_err(|e| {
|
||||
crate::executor::SpawnContext::from_command(&command, "OpenCode")
|
||||
.with_task(task_id, Some(task.title.clone()))
|
||||
.with_context("OpenCode CLI execution for new task")
|
||||
.spawn_error(e)
|
||||
})?;
|
||||
|
||||
Ok(child)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Executor for OpencodeFollowupExecutor {
|
||||
async fn spawn(
|
||||
&self,
|
||||
_pool: &sqlx::SqlitePool,
|
||||
_task_id: Uuid,
|
||||
worktree_path: &str,
|
||||
) -> Result<AsyncGroupChild, ExecutorError> {
|
||||
use std::process::Stdio;
|
||||
|
||||
use tokio::process::Command;
|
||||
|
||||
// Use shell command for cross-platform compatibility
|
||||
let (shell_cmd, shell_arg) = get_shell_command();
|
||||
let opencode_command = format!("opencode -p \"{}\"", self.prompt.replace('"', "\\\""));
|
||||
|
||||
let mut command = Command::new(shell_cmd);
|
||||
command
|
||||
.kill_on_drop(true)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.current_dir(worktree_path)
|
||||
.arg(shell_arg)
|
||||
.arg(&opencode_command);
|
||||
|
||||
let child = command
|
||||
.group_spawn() // Create new process group so we can kill entire tree
|
||||
.map_err(|e| {
|
||||
crate::executor::SpawnContext::from_command(&command, "OpenCode")
|
||||
.with_context(format!(
|
||||
"OpenCode CLI followup execution for session {}",
|
||||
self.session_id
|
||||
))
|
||||
.spawn_error(e)
|
||||
})?;
|
||||
|
||||
Ok(child)
|
||||
}
|
||||
}
|
||||
@@ -731,6 +731,7 @@ impl TaskAttempt {
|
||||
crate::executor::ExecutorConfig::Claude => "claude",
|
||||
crate::executor::ExecutorConfig::Amp => "amp",
|
||||
crate::executor::ExecutorConfig::Gemini => "gemini",
|
||||
crate::executor::ExecutorConfig::Opencode => "opencode",
|
||||
};
|
||||
(
|
||||
"executor".to_string(),
|
||||
@@ -744,6 +745,7 @@ impl TaskAttempt {
|
||||
crate::executor::ExecutorConfig::Claude => "claude",
|
||||
crate::executor::ExecutorConfig::Amp => "amp",
|
||||
crate::executor::ExecutorConfig::Gemini => "gemini",
|
||||
crate::executor::ExecutorConfig::Opencode => "opencode",
|
||||
};
|
||||
(
|
||||
"followup_executor".to_string(),
|
||||
@@ -864,7 +866,7 @@ impl TaskAttempt {
|
||||
prompt,
|
||||
} => {
|
||||
use crate::executors::{
|
||||
AmpFollowupExecutor, ClaudeFollowupExecutor, GeminiFollowupExecutor,
|
||||
AmpFollowupExecutor, ClaudeFollowupExecutor, GeminiFollowupExecutor, OpencodeFollowupExecutor,
|
||||
};
|
||||
|
||||
let executor: Box<dyn crate::executor::Executor> = match config {
|
||||
@@ -902,6 +904,16 @@ impl TaskAttempt {
|
||||
// Echo doesn't support followup, use regular echo
|
||||
config.create_executor()
|
||||
}
|
||||
crate::executor::ExecutorConfig::Opencode => {
|
||||
if let Some(sid) = session_id {
|
||||
Box::new(OpencodeFollowupExecutor {
|
||||
session_id: sid.clone(),
|
||||
prompt: prompt.clone(),
|
||||
})
|
||||
} else {
|
||||
return Err(TaskAttemptError::TaskNotFound); // No session ID for followup
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
executor
|
||||
|
||||
@@ -142,6 +142,7 @@ pub async fn create_task_and_start(
|
||||
crate::executor::ExecutorConfig::Claude => "claude".to_string(),
|
||||
crate::executor::ExecutorConfig::Amp => "amp".to_string(),
|
||||
crate::executor::ExecutorConfig::Gemini => "gemini".to_string(),
|
||||
crate::executor::ExecutorConfig::Opencode => "opencode".to_string(),
|
||||
});
|
||||
let attempt_payload = CreateTaskAttempt {
|
||||
executor: executor_string,
|
||||
|
||||
@@ -20,7 +20,7 @@ export type SoundConstants = { sound_files: Array<SoundFile>, sound_labels: Arra
|
||||
|
||||
export type ConfigConstants = { editor: EditorConstants, sound: SoundConstants, };
|
||||
|
||||
export type ExecutorConfig = { "type": "echo" } | { "type": "claude" } | { "type": "amp" } | { "type": "gemini" };
|
||||
export type ExecutorConfig = { "type": "echo" } | { "type": "claude" } | { "type": "amp" } | { "type": "gemini" } | { "type": "opencode" };
|
||||
|
||||
export type ExecutorConstants = { executor_types: Array<ExecutorConfig>, executor_labels: Array<string>, };
|
||||
|
||||
@@ -99,7 +99,8 @@ export const EXECUTOR_TYPES: string[] = [
|
||||
"echo",
|
||||
"claude",
|
||||
"amp",
|
||||
"gemini"
|
||||
"gemini",
|
||||
"opencode"
|
||||
];
|
||||
|
||||
export const EDITOR_TYPES: EditorType[] = [
|
||||
@@ -115,7 +116,8 @@ export const EXECUTOR_LABELS: Record<string, string> = {
|
||||
"echo": "Echo (Test Mode)",
|
||||
"claude": "Claude",
|
||||
"amp": "Amp",
|
||||
"gemini": "Gemini"
|
||||
"gemini": "Gemini",
|
||||
"opencode": "OpenCode"
|
||||
};
|
||||
|
||||
export const EDITOR_LABELS: Record<string, string> = {
|
||||
|
||||
Reference in New Issue
Block a user