Task attempt 2c218bb9-0865-4b9e-8ecb-0d60ed20ca19 - Final changes
This commit is contained in:
@@ -8,7 +8,8 @@ export const EXECUTOR_TYPES: string[] = [
|
|||||||
"echo",
|
"echo",
|
||||||
"claude",
|
"claude",
|
||||||
"amp",
|
"amp",
|
||||||
"gemini"
|
"gemini",
|
||||||
|
"opencode"
|
||||||
];
|
];
|
||||||
|
|
||||||
export const EDITOR_TYPES: EditorType[] = [
|
export const EDITOR_TYPES: EditorType[] = [
|
||||||
@@ -24,7 +25,8 @@ export const EXECUTOR_LABELS: Record<string, string> = {
|
|||||||
"echo": "Echo (Test Mode)",
|
"echo": "Echo (Test Mode)",
|
||||||
"claude": "Claude",
|
"claude": "Claude",
|
||||||
"amp": "Amp",
|
"amp": "Amp",
|
||||||
"gemini": "Gemini"
|
"gemini": "Gemini",
|
||||||
|
"opencode": "OpenCode"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EDITOR_LABELS: Record<string, string> = {
|
export const EDITOR_LABELS: Record<string, string> = {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use tokio::io::{AsyncBufReadExt, BufReader};
|
|||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
use uuid::Uuid;
|
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
|
/// Context information for spawn failures to provide comprehensive error details
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -231,6 +231,7 @@ pub enum ExecutorConfig {
|
|||||||
Claude,
|
Claude,
|
||||||
Amp,
|
Amp,
|
||||||
Gemini,
|
Gemini,
|
||||||
|
Opencode,
|
||||||
// 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 },
|
||||||
@@ -251,6 +252,7 @@ impl ExecutorConfig {
|
|||||||
ExecutorConfig::Claude => Box::new(ClaudeExecutor),
|
ExecutorConfig::Claude => Box::new(ClaudeExecutor),
|
||||||
ExecutorConfig::Amp => Box::new(AmpExecutor),
|
ExecutorConfig::Amp => Box::new(AmpExecutor),
|
||||||
ExecutorConfig::Gemini => Box::new(GeminiExecutor),
|
ExecutorConfig::Gemini => Box::new(GeminiExecutor),
|
||||||
|
ExecutorConfig::Opencode => Box::new(OpencodeExecutor),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ pub mod claude;
|
|||||||
pub mod dev_server;
|
pub mod dev_server;
|
||||||
pub mod echo;
|
pub mod echo;
|
||||||
pub mod gemini;
|
pub mod gemini;
|
||||||
|
pub mod opencode;
|
||||||
pub mod setup_script;
|
pub mod setup_script;
|
||||||
|
|
||||||
pub use amp::{AmpExecutor, AmpFollowupExecutor};
|
pub use amp::{AmpExecutor, AmpFollowupExecutor};
|
||||||
@@ -10,4 +11,5 @@ pub use claude::{ClaudeExecutor, ClaudeFollowupExecutor};
|
|||||||
pub use dev_server::DevServerExecutor;
|
pub use dev_server::DevServerExecutor;
|
||||||
pub use echo::EchoExecutor;
|
pub use echo::EchoExecutor;
|
||||||
pub use gemini::{GeminiExecutor, GeminiFollowupExecutor};
|
pub use gemini::{GeminiExecutor, GeminiFollowupExecutor};
|
||||||
|
pub use opencode::{OpencodeExecutor, OpencodeFollowupExecutor};
|
||||||
pub use setup_script::SetupScriptExecutor;
|
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::Claude => "claude",
|
||||||
crate::executor::ExecutorConfig::Amp => "amp",
|
crate::executor::ExecutorConfig::Amp => "amp",
|
||||||
crate::executor::ExecutorConfig::Gemini => "gemini",
|
crate::executor::ExecutorConfig::Gemini => "gemini",
|
||||||
|
crate::executor::ExecutorConfig::Opencode => "opencode",
|
||||||
};
|
};
|
||||||
(
|
(
|
||||||
"executor".to_string(),
|
"executor".to_string(),
|
||||||
@@ -744,6 +745,7 @@ impl TaskAttempt {
|
|||||||
crate::executor::ExecutorConfig::Claude => "claude",
|
crate::executor::ExecutorConfig::Claude => "claude",
|
||||||
crate::executor::ExecutorConfig::Amp => "amp",
|
crate::executor::ExecutorConfig::Amp => "amp",
|
||||||
crate::executor::ExecutorConfig::Gemini => "gemini",
|
crate::executor::ExecutorConfig::Gemini => "gemini",
|
||||||
|
crate::executor::ExecutorConfig::Opencode => "opencode",
|
||||||
};
|
};
|
||||||
(
|
(
|
||||||
"followup_executor".to_string(),
|
"followup_executor".to_string(),
|
||||||
@@ -864,7 +866,7 @@ impl TaskAttempt {
|
|||||||
prompt,
|
prompt,
|
||||||
} => {
|
} => {
|
||||||
use crate::executors::{
|
use crate::executors::{
|
||||||
AmpFollowupExecutor, ClaudeFollowupExecutor, GeminiFollowupExecutor,
|
AmpFollowupExecutor, ClaudeFollowupExecutor, GeminiFollowupExecutor, OpencodeFollowupExecutor,
|
||||||
};
|
};
|
||||||
|
|
||||||
let executor: Box<dyn crate::executor::Executor> = match config {
|
let executor: Box<dyn crate::executor::Executor> = match config {
|
||||||
@@ -902,6 +904,16 @@ impl TaskAttempt {
|
|||||||
// Echo doesn't support followup, use regular echo
|
// Echo doesn't support followup, use regular echo
|
||||||
config.create_executor()
|
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
|
executor
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ pub async fn create_task_and_start(
|
|||||||
crate::executor::ExecutorConfig::Claude => "claude".to_string(),
|
crate::executor::ExecutorConfig::Claude => "claude".to_string(),
|
||||||
crate::executor::ExecutorConfig::Amp => "amp".to_string(),
|
crate::executor::ExecutorConfig::Amp => "amp".to_string(),
|
||||||
crate::executor::ExecutorConfig::Gemini => "gemini".to_string(),
|
crate::executor::ExecutorConfig::Gemini => "gemini".to_string(),
|
||||||
|
crate::executor::ExecutorConfig::Opencode => "opencode".to_string(),
|
||||||
});
|
});
|
||||||
let attempt_payload = CreateTaskAttempt {
|
let attempt_payload = CreateTaskAttempt {
|
||||||
executor: executor_string,
|
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 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>, };
|
export type ExecutorConstants = { executor_types: Array<ExecutorConfig>, executor_labels: Array<string>, };
|
||||||
|
|
||||||
@@ -99,7 +99,8 @@ export const EXECUTOR_TYPES: string[] = [
|
|||||||
"echo",
|
"echo",
|
||||||
"claude",
|
"claude",
|
||||||
"amp",
|
"amp",
|
||||||
"gemini"
|
"gemini",
|
||||||
|
"opencode"
|
||||||
];
|
];
|
||||||
|
|
||||||
export const EDITOR_TYPES: EditorType[] = [
|
export const EDITOR_TYPES: EditorType[] = [
|
||||||
@@ -115,7 +116,8 @@ export const EXECUTOR_LABELS: Record<string, string> = {
|
|||||||
"echo": "Echo (Test Mode)",
|
"echo": "Echo (Test Mode)",
|
||||||
"claude": "Claude",
|
"claude": "Claude",
|
||||||
"amp": "Amp",
|
"amp": "Amp",
|
||||||
"gemini": "Gemini"
|
"gemini": "Gemini",
|
||||||
|
"opencode": "OpenCode"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EDITOR_LABELS: Record<string, string> = {
|
export const EDITOR_LABELS: Record<string, string> = {
|
||||||
|
|||||||
Reference in New Issue
Block a user