mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-11 12:03:31 +08:00
feat: add ecc2 codex and opencode runners
This commit is contained in:
@@ -10,7 +10,7 @@ use super::output::SessionOutputStore;
|
|||||||
use super::runtime::capture_command_output;
|
use super::runtime::capture_command_output;
|
||||||
use super::store::StateStore;
|
use super::store::StateStore;
|
||||||
use super::{
|
use super::{
|
||||||
default_project_label, default_task_group_label, normalize_group_label, Session,
|
default_project_label, default_task_group_label, normalize_group_label, HarnessKind, Session,
|
||||||
SessionAgentProfile, SessionGrouping, SessionHarnessInfo, SessionMetrics, SessionState,
|
SessionAgentProfile, SessionGrouping, SessionHarnessInfo, SessionMetrics, SessionState,
|
||||||
};
|
};
|
||||||
use crate::comms::{self, MessageType};
|
use crate::comms::{self, MessageType};
|
||||||
@@ -1897,8 +1897,10 @@ pub async fn delete_session(db: &StateStore, id: &str) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn agent_program(agent_type: &str) -> Result<PathBuf> {
|
fn agent_program(agent_type: &str) -> Result<PathBuf> {
|
||||||
match agent_type {
|
match HarnessKind::from_agent_type(agent_type) {
|
||||||
"claude" => Ok(PathBuf::from("claude")),
|
HarnessKind::Claude => Ok(PathBuf::from("claude")),
|
||||||
|
HarnessKind::Codex => Ok(PathBuf::from("codex")),
|
||||||
|
HarnessKind::OpenCode => Ok(PathBuf::from("opencode")),
|
||||||
other => anyhow::bail!("Unsupported agent type: {other}"),
|
other => anyhow::bail!("Unsupported agent type: {other}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1935,6 +1937,7 @@ pub async fn run_session(
|
|||||||
let agent_program = agent_program(agent_type)?;
|
let agent_program = agent_program(agent_type)?;
|
||||||
let profile = db.get_session_profile(session_id)?;
|
let profile = db.get_session_profile(session_id)?;
|
||||||
let command = build_agent_command(
|
let command = build_agent_command(
|
||||||
|
agent_type,
|
||||||
&agent_program,
|
&agent_program,
|
||||||
task,
|
task,
|
||||||
session_id,
|
session_id,
|
||||||
@@ -2521,46 +2524,86 @@ async fn spawn_session_runner_for_program(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build_agent_command(
|
fn build_agent_command(
|
||||||
|
agent_type: &str,
|
||||||
agent_program: &Path,
|
agent_program: &Path,
|
||||||
task: &str,
|
task: &str,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
working_dir: &Path,
|
working_dir: &Path,
|
||||||
profile: Option<&SessionAgentProfile>,
|
profile: Option<&SessionAgentProfile>,
|
||||||
) -> Command {
|
) -> Command {
|
||||||
|
let harness = HarnessKind::from_agent_type(agent_type);
|
||||||
|
let task = normalize_task_for_harness(harness, task, profile);
|
||||||
let mut command = Command::new(agent_program);
|
let mut command = Command::new(agent_program);
|
||||||
command.env("ECC_SESSION_ID", session_id);
|
command.env("ECC_SESSION_ID", session_id);
|
||||||
command
|
match harness {
|
||||||
.arg("--print")
|
HarnessKind::Claude => {
|
||||||
.arg("--name")
|
|
||||||
.arg(format!("ecc-{session_id}"));
|
|
||||||
if let Some(profile) = profile {
|
|
||||||
if let Some(model) = profile.model.as_ref() {
|
|
||||||
command.arg("--model").arg(model);
|
|
||||||
}
|
|
||||||
if !profile.allowed_tools.is_empty() {
|
|
||||||
command
|
command
|
||||||
.arg("--allowed-tools")
|
.arg("--print")
|
||||||
.arg(profile.allowed_tools.join(","));
|
.arg("--name")
|
||||||
|
.arg(format!("ecc-{session_id}"));
|
||||||
|
if let Some(profile) = profile {
|
||||||
|
if let Some(model) = profile.model.as_ref() {
|
||||||
|
command.arg("--model").arg(model);
|
||||||
|
}
|
||||||
|
if !profile.allowed_tools.is_empty() {
|
||||||
|
command
|
||||||
|
.arg("--allowed-tools")
|
||||||
|
.arg(profile.allowed_tools.join(","));
|
||||||
|
}
|
||||||
|
if !profile.disallowed_tools.is_empty() {
|
||||||
|
command
|
||||||
|
.arg("--disallowed-tools")
|
||||||
|
.arg(profile.disallowed_tools.join(","));
|
||||||
|
}
|
||||||
|
if let Some(permission_mode) = profile.permission_mode.as_ref() {
|
||||||
|
command.arg("--permission-mode").arg(permission_mode);
|
||||||
|
}
|
||||||
|
for dir in &profile.add_dirs {
|
||||||
|
command.arg("--add-dir").arg(dir);
|
||||||
|
}
|
||||||
|
if let Some(max_budget_usd) = profile.max_budget_usd {
|
||||||
|
command
|
||||||
|
.arg("--max-budget-usd")
|
||||||
|
.arg(max_budget_usd.to_string());
|
||||||
|
}
|
||||||
|
if let Some(prompt) = profile.append_system_prompt.as_ref() {
|
||||||
|
command.arg("--append-system-prompt").arg(prompt);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !profile.disallowed_tools.is_empty() {
|
HarnessKind::Codex => {
|
||||||
command
|
command
|
||||||
.arg("--disallowed-tools")
|
.arg("exec")
|
||||||
.arg(profile.disallowed_tools.join(","));
|
.arg("--skip-git-repo-check")
|
||||||
|
.arg("--sandbox")
|
||||||
|
.arg("workspace-write")
|
||||||
|
.arg("--cd")
|
||||||
|
.arg(working_dir)
|
||||||
|
.arg("--color")
|
||||||
|
.arg("never");
|
||||||
|
if let Some(profile) = profile {
|
||||||
|
if let Some(model) = profile.model.as_ref() {
|
||||||
|
command.arg("--model").arg(model);
|
||||||
|
}
|
||||||
|
for dir in &profile.add_dirs {
|
||||||
|
command.arg("--add-dir").arg(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let Some(permission_mode) = profile.permission_mode.as_ref() {
|
HarnessKind::OpenCode => {
|
||||||
command.arg("--permission-mode").arg(permission_mode);
|
|
||||||
}
|
|
||||||
for dir in &profile.add_dirs {
|
|
||||||
command.arg("--add-dir").arg(dir);
|
|
||||||
}
|
|
||||||
if let Some(max_budget_usd) = profile.max_budget_usd {
|
|
||||||
command
|
command
|
||||||
.arg("--max-budget-usd")
|
.arg("run")
|
||||||
.arg(max_budget_usd.to_string());
|
.arg("--dir")
|
||||||
}
|
.arg(working_dir)
|
||||||
if let Some(prompt) = profile.append_system_prompt.as_ref() {
|
.arg("--title")
|
||||||
command.arg("--append-system-prompt").arg(prompt);
|
.arg(format!("ecc-{session_id}"));
|
||||||
|
if let Some(profile) = profile {
|
||||||
|
if let Some(model) = profile.model.as_ref() {
|
||||||
|
command.arg("--model").arg(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
command
|
command
|
||||||
.arg(task)
|
.arg(task)
|
||||||
@@ -2569,13 +2612,33 @@ fn build_agent_command(
|
|||||||
command
|
command
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn normalize_task_for_harness(
|
||||||
|
harness: HarnessKind,
|
||||||
|
task: &str,
|
||||||
|
profile: Option<&SessionAgentProfile>,
|
||||||
|
) -> String {
|
||||||
|
let Some(system_prompt) = profile.and_then(|profile| profile.append_system_prompt.as_ref())
|
||||||
|
else {
|
||||||
|
return task.to_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
match harness {
|
||||||
|
HarnessKind::Claude => task.to_string(),
|
||||||
|
HarnessKind::Codex | HarnessKind::OpenCode => {
|
||||||
|
format!("System instructions:\n{system_prompt}\n\nTask:\n{task}")
|
||||||
|
}
|
||||||
|
_ => task.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn spawn_claude_code(
|
async fn spawn_claude_code(
|
||||||
agent_program: &Path,
|
agent_program: &Path,
|
||||||
task: &str,
|
task: &str,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
working_dir: &Path,
|
working_dir: &Path,
|
||||||
) -> Result<u32> {
|
) -> Result<u32> {
|
||||||
let mut command = build_agent_command(agent_program, task, session_id, working_dir, None);
|
let mut command =
|
||||||
|
build_agent_command("claude", agent_program, task, session_id, working_dir, None);
|
||||||
let child = command
|
let child = command
|
||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null())
|
||||||
@@ -3302,7 +3365,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn build_agent_command_applies_profile_runner_flags() {
|
fn build_agent_command_applies_profile_runner_flags_for_claude() {
|
||||||
let profile = SessionAgentProfile {
|
let profile = SessionAgentProfile {
|
||||||
profile_name: "reviewer".to_string(),
|
profile_name: "reviewer".to_string(),
|
||||||
agent: None,
|
agent: None,
|
||||||
@@ -3317,6 +3380,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let command = build_agent_command(
|
let command = build_agent_command(
|
||||||
|
"claude",
|
||||||
Path::new("claude"),
|
Path::new("claude"),
|
||||||
"review this change",
|
"review this change",
|
||||||
"sess-1234",
|
"sess-1234",
|
||||||
@@ -3356,6 +3420,101 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn build_agent_command_normalizes_runner_flags_for_codex() {
|
||||||
|
let profile = SessionAgentProfile {
|
||||||
|
profile_name: "reviewer".to_string(),
|
||||||
|
agent: None,
|
||||||
|
model: Some("gpt-5.4".to_string()),
|
||||||
|
allowed_tools: vec!["Read".to_string()],
|
||||||
|
disallowed_tools: vec!["Bash".to_string()],
|
||||||
|
permission_mode: Some("plan".to_string()),
|
||||||
|
add_dirs: vec![PathBuf::from("docs"), PathBuf::from("specs")],
|
||||||
|
max_budget_usd: Some(1.25),
|
||||||
|
token_budget: Some(750),
|
||||||
|
append_system_prompt: Some("Review thoroughly.".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let command = build_agent_command(
|
||||||
|
"codex",
|
||||||
|
Path::new("codex"),
|
||||||
|
"review this change",
|
||||||
|
"sess-1234",
|
||||||
|
Path::new("/tmp/repo"),
|
||||||
|
Some(&profile),
|
||||||
|
);
|
||||||
|
let args = command
|
||||||
|
.as_std()
|
||||||
|
.get_args()
|
||||||
|
.map(|value| value.to_string_lossy().to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
args,
|
||||||
|
vec![
|
||||||
|
"exec",
|
||||||
|
"--skip-git-repo-check",
|
||||||
|
"--sandbox",
|
||||||
|
"workspace-write",
|
||||||
|
"--cd",
|
||||||
|
"/tmp/repo",
|
||||||
|
"--color",
|
||||||
|
"never",
|
||||||
|
"--model",
|
||||||
|
"gpt-5.4",
|
||||||
|
"--add-dir",
|
||||||
|
"docs",
|
||||||
|
"--add-dir",
|
||||||
|
"specs",
|
||||||
|
"System instructions:\nReview thoroughly.\n\nTask:\nreview this change",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn build_agent_command_normalizes_runner_flags_for_opencode() {
|
||||||
|
let profile = SessionAgentProfile {
|
||||||
|
profile_name: "builder".to_string(),
|
||||||
|
agent: None,
|
||||||
|
model: Some("anthropic/claude-sonnet-4".to_string()),
|
||||||
|
allowed_tools: Vec::new(),
|
||||||
|
disallowed_tools: Vec::new(),
|
||||||
|
permission_mode: None,
|
||||||
|
add_dirs: vec![PathBuf::from("docs")],
|
||||||
|
max_budget_usd: None,
|
||||||
|
token_budget: None,
|
||||||
|
append_system_prompt: Some("Build carefully.".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let command = build_agent_command(
|
||||||
|
"opencode",
|
||||||
|
Path::new("opencode"),
|
||||||
|
"stabilize callback flow",
|
||||||
|
"sess-9999",
|
||||||
|
Path::new("/tmp/repo"),
|
||||||
|
Some(&profile),
|
||||||
|
);
|
||||||
|
let args = command
|
||||||
|
.as_std()
|
||||||
|
.get_args()
|
||||||
|
.map(|value| value.to_string_lossy().to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
args,
|
||||||
|
vec![
|
||||||
|
"run",
|
||||||
|
"--dir",
|
||||||
|
"/tmp/repo",
|
||||||
|
"--title",
|
||||||
|
"ecc-sess-9999",
|
||||||
|
"--model",
|
||||||
|
"anthropic/claude-sonnet-4",
|
||||||
|
"System instructions:\nBuild carefully.\n\nTask:\nstabilize callback flow",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn enforce_session_heartbeats_marks_overdue_running_sessions_stale() -> Result<()> {
|
fn enforce_session_heartbeats_marks_overdue_running_sessions_stale() -> Result<()> {
|
||||||
let tempdir = TestDir::new("manager-heartbeat-stale")?;
|
let tempdir = TestDir::new("manager-heartbeat-stale")?;
|
||||||
|
|||||||
Reference in New Issue
Block a user