mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-11 20:13:30 +08:00
feat: add ecc2 configurable harness runners
This commit is contained in:
@@ -79,6 +79,26 @@ pub struct ResolvedAgentProfile {
|
|||||||
pub append_system_prompt: Option<String>,
|
pub append_system_prompt: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct HarnessRunnerConfig {
|
||||||
|
pub program: String,
|
||||||
|
pub base_args: Vec<String>,
|
||||||
|
pub cwd_flag: Option<String>,
|
||||||
|
pub session_name_flag: Option<String>,
|
||||||
|
pub task_flag: Option<String>,
|
||||||
|
pub model_flag: Option<String>,
|
||||||
|
pub add_dir_flag: Option<String>,
|
||||||
|
pub include_directories_flag: Option<String>,
|
||||||
|
pub allowed_tools_flag: Option<String>,
|
||||||
|
pub disallowed_tools_flag: Option<String>,
|
||||||
|
pub permission_mode_flag: Option<String>,
|
||||||
|
pub max_budget_usd_flag: Option<String>,
|
||||||
|
pub append_system_prompt_flag: Option<String>,
|
||||||
|
pub inline_system_prompt_for_task: bool,
|
||||||
|
pub env: BTreeMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct OrchestrationTemplateConfig {
|
pub struct OrchestrationTemplateConfig {
|
||||||
@@ -198,6 +218,7 @@ pub struct Config {
|
|||||||
pub auto_terminate_stale_sessions: bool,
|
pub auto_terminate_stale_sessions: bool,
|
||||||
pub default_agent: String,
|
pub default_agent: String,
|
||||||
pub default_agent_profile: Option<String>,
|
pub default_agent_profile: Option<String>,
|
||||||
|
pub harness_runners: BTreeMap<String, HarnessRunnerConfig>,
|
||||||
pub agent_profiles: BTreeMap<String, AgentProfileConfig>,
|
pub agent_profiles: BTreeMap<String, AgentProfileConfig>,
|
||||||
pub orchestration_templates: BTreeMap<String, OrchestrationTemplateConfig>,
|
pub orchestration_templates: BTreeMap<String, OrchestrationTemplateConfig>,
|
||||||
pub memory_connectors: BTreeMap<String, MemoryConnectorConfig>,
|
pub memory_connectors: BTreeMap<String, MemoryConnectorConfig>,
|
||||||
@@ -263,6 +284,7 @@ impl Default for Config {
|
|||||||
auto_terminate_stale_sessions: false,
|
auto_terminate_stale_sessions: false,
|
||||||
default_agent: "claude".to_string(),
|
default_agent: "claude".to_string(),
|
||||||
default_agent_profile: None,
|
default_agent_profile: None,
|
||||||
|
harness_runners: BTreeMap::new(),
|
||||||
agent_profiles: BTreeMap::new(),
|
agent_profiles: BTreeMap::new(),
|
||||||
orchestration_templates: BTreeMap::new(),
|
orchestration_templates: BTreeMap::new(),
|
||||||
memory_connectors: BTreeMap::new(),
|
memory_connectors: BTreeMap::new(),
|
||||||
@@ -329,6 +351,11 @@ impl Config {
|
|||||||
self.resolve_agent_profile_inner(name, &mut chain)
|
self.resolve_agent_profile_inner(name, &mut chain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn harness_runner(&self, harness: &str) -> Option<&HarnessRunnerConfig> {
|
||||||
|
let key = harness.trim().to_ascii_lowercase();
|
||||||
|
self.harness_runners.get(&key)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn resolve_orchestration_template(
|
pub fn resolve_orchestration_template(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
@@ -720,6 +747,28 @@ impl ResolvedAgentProfile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for HarnessRunnerConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
program: String::new(),
|
||||||
|
base_args: Vec::new(),
|
||||||
|
cwd_flag: None,
|
||||||
|
session_name_flag: None,
|
||||||
|
task_flag: None,
|
||||||
|
model_flag: None,
|
||||||
|
add_dir_flag: None,
|
||||||
|
include_directories_flag: None,
|
||||||
|
allowed_tools_flag: None,
|
||||||
|
disallowed_tools_flag: None,
|
||||||
|
permission_mode_flag: None,
|
||||||
|
max_budget_usd_flag: None,
|
||||||
|
append_system_prompt_flag: None,
|
||||||
|
inline_system_prompt_for_task: true,
|
||||||
|
env: BTreeMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn merge_unique<T>(base: &mut Vec<T>, additions: &[T])
|
fn merge_unique<T>(base: &mut Vec<T>, additions: &[T])
|
||||||
where
|
where
|
||||||
T: Clone + PartialEq,
|
T: Clone + PartialEq,
|
||||||
@@ -1210,6 +1259,44 @@ inherits = "a"
|
|||||||
.contains("agent profile inheritance cycle"));
|
.contains("agent profile inheritance cycle"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn harness_runners_deserialize_from_toml() {
|
||||||
|
let config: Config = toml::from_str(
|
||||||
|
r#"
|
||||||
|
[harness_runners.cursor]
|
||||||
|
program = "cursor-agent"
|
||||||
|
base_args = ["run"]
|
||||||
|
cwd_flag = "--cwd"
|
||||||
|
session_name_flag = "--name"
|
||||||
|
task_flag = "--task"
|
||||||
|
model_flag = "--model"
|
||||||
|
permission_mode_flag = "--permission-mode"
|
||||||
|
inline_system_prompt_for_task = true
|
||||||
|
|
||||||
|
[harness_runners.cursor.env]
|
||||||
|
ECC_HARNESS = "cursor"
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let runner = config.harness_runner("cursor").expect("cursor runner");
|
||||||
|
assert_eq!(runner.program, "cursor-agent");
|
||||||
|
assert_eq!(runner.base_args, vec!["run"]);
|
||||||
|
assert_eq!(runner.cwd_flag.as_deref(), Some("--cwd"));
|
||||||
|
assert_eq!(runner.session_name_flag.as_deref(), Some("--name"));
|
||||||
|
assert_eq!(runner.task_flag.as_deref(), Some("--task"));
|
||||||
|
assert_eq!(runner.model_flag.as_deref(), Some("--model"));
|
||||||
|
assert_eq!(
|
||||||
|
runner.permission_mode_flag.as_deref(),
|
||||||
|
Some("--permission-mode")
|
||||||
|
);
|
||||||
|
assert!(runner.inline_system_prompt_for_task);
|
||||||
|
assert_eq!(
|
||||||
|
runner.env.get("ECC_HARNESS").map(String::as_str),
|
||||||
|
Some("cursor")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn orchestration_templates_resolve_steps_and_interpolate_variables() {
|
fn orchestration_templates_resolve_steps_and_interpolate_variables() {
|
||||||
let config: Config = toml::from_str(
|
let config: Config = toml::from_str(
|
||||||
|
|||||||
@@ -2002,8 +2002,17 @@ pub async fn delete_session(db: &StateStore, id: &str) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn agent_program(agent_type: &str) -> Result<PathBuf> {
|
fn agent_program(cfg: &Config, agent_type: &str) -> Result<PathBuf> {
|
||||||
match HarnessKind::from_agent_type(agent_type) {
|
let harness = HarnessKind::from_agent_type(agent_type);
|
||||||
|
if let Some(runner) = cfg.harness_runner(harness.as_str()) {
|
||||||
|
let program = runner.program.trim();
|
||||||
|
if program.is_empty() {
|
||||||
|
anyhow::bail!("Configured harness runner for {harness} is missing a program");
|
||||||
|
}
|
||||||
|
return Ok(PathBuf::from(program));
|
||||||
|
}
|
||||||
|
|
||||||
|
match harness {
|
||||||
HarnessKind::Claude => Ok(PathBuf::from("claude")),
|
HarnessKind::Claude => Ok(PathBuf::from("claude")),
|
||||||
HarnessKind::Codex => Ok(PathBuf::from("codex")),
|
HarnessKind::Codex => Ok(PathBuf::from("codex")),
|
||||||
HarnessKind::OpenCode => Ok(PathBuf::from("opencode")),
|
HarnessKind::OpenCode => Ok(PathBuf::from("opencode")),
|
||||||
@@ -2067,9 +2076,10 @@ pub async fn run_session(
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let agent_program = agent_program(agent_type)?;
|
let agent_program = agent_program(cfg, 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(
|
||||||
|
cfg,
|
||||||
agent_type,
|
agent_type,
|
||||||
&agent_program,
|
&agent_program,
|
||||||
task,
|
task,
|
||||||
@@ -2666,6 +2676,7 @@ async fn spawn_session_runner_for_program(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build_agent_command(
|
fn build_agent_command(
|
||||||
|
cfg: &Config,
|
||||||
agent_type: &str,
|
agent_type: &str,
|
||||||
agent_program: &Path,
|
agent_program: &Path,
|
||||||
task: &str,
|
task: &str,
|
||||||
@@ -2674,6 +2685,17 @@ fn build_agent_command(
|
|||||||
profile: Option<&SessionAgentProfile>,
|
profile: Option<&SessionAgentProfile>,
|
||||||
) -> Command {
|
) -> Command {
|
||||||
let harness = HarnessKind::from_agent_type(agent_type);
|
let harness = HarnessKind::from_agent_type(agent_type);
|
||||||
|
if let Some(runner) = cfg.harness_runner(harness.as_str()) {
|
||||||
|
return build_configured_harness_command(
|
||||||
|
runner,
|
||||||
|
agent_program,
|
||||||
|
task,
|
||||||
|
session_id,
|
||||||
|
working_dir,
|
||||||
|
profile,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let task = normalize_task_for_harness(harness, task, profile);
|
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);
|
||||||
@@ -2771,23 +2793,122 @@ fn build_agent_command(
|
|||||||
command
|
command
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_configured_harness_command(
|
||||||
|
runner: &crate::config::HarnessRunnerConfig,
|
||||||
|
agent_program: &Path,
|
||||||
|
task: &str,
|
||||||
|
session_id: &str,
|
||||||
|
working_dir: &Path,
|
||||||
|
profile: Option<&SessionAgentProfile>,
|
||||||
|
) -> Command {
|
||||||
|
let mut command = Command::new(agent_program);
|
||||||
|
command.env("ECC_SESSION_ID", session_id);
|
||||||
|
for (key, value) in &runner.env {
|
||||||
|
if !value.trim().is_empty() {
|
||||||
|
command.env(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for arg in &runner.base_args {
|
||||||
|
if !arg.trim().is_empty() {
|
||||||
|
command.arg(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(flag) = runner.cwd_flag.as_deref() {
|
||||||
|
command.arg(flag).arg(working_dir);
|
||||||
|
}
|
||||||
|
if let Some(flag) = runner.session_name_flag.as_deref() {
|
||||||
|
command.arg(flag).arg(format!("ecc-{session_id}"));
|
||||||
|
}
|
||||||
|
if let Some(profile) = profile {
|
||||||
|
if let (Some(flag), Some(model)) = (runner.model_flag.as_deref(), profile.model.as_ref()) {
|
||||||
|
command.arg(flag).arg(model);
|
||||||
|
}
|
||||||
|
if let Some(flag) = runner.add_dir_flag.as_deref() {
|
||||||
|
for dir in &profile.add_dirs {
|
||||||
|
command.arg(flag).arg(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(flag) = runner.include_directories_flag.as_deref() {
|
||||||
|
if !profile.add_dirs.is_empty() {
|
||||||
|
let include_dirs = profile
|
||||||
|
.add_dirs
|
||||||
|
.iter()
|
||||||
|
.map(|dir| dir.to_string_lossy().to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(",");
|
||||||
|
command.arg(flag).arg(include_dirs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(flag) = runner.allowed_tools_flag.as_deref() {
|
||||||
|
if !profile.allowed_tools.is_empty() {
|
||||||
|
command.arg(flag).arg(profile.allowed_tools.join(","));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(flag) = runner.disallowed_tools_flag.as_deref() {
|
||||||
|
if !profile.disallowed_tools.is_empty() {
|
||||||
|
command.arg(flag).arg(profile.disallowed_tools.join(","));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let (Some(flag), Some(permission_mode)) = (
|
||||||
|
runner.permission_mode_flag.as_deref(),
|
||||||
|
profile.permission_mode.as_ref(),
|
||||||
|
) {
|
||||||
|
command.arg(flag).arg(permission_mode);
|
||||||
|
}
|
||||||
|
if let (Some(flag), Some(max_budget_usd)) = (
|
||||||
|
runner.max_budget_usd_flag.as_deref(),
|
||||||
|
profile.max_budget_usd,
|
||||||
|
) {
|
||||||
|
command.arg(flag).arg(max_budget_usd.to_string());
|
||||||
|
}
|
||||||
|
if let (Some(flag), Some(prompt)) = (
|
||||||
|
runner.append_system_prompt_flag.as_deref(),
|
||||||
|
profile.append_system_prompt.as_ref(),
|
||||||
|
) {
|
||||||
|
command.arg(flag).arg(prompt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let task = if runner.inline_system_prompt_for_task && runner.append_system_prompt_flag.is_none()
|
||||||
|
{
|
||||||
|
normalize_task_with_inline_system_prompt(task, profile)
|
||||||
|
} else {
|
||||||
|
task.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(flag) = runner.task_flag.as_deref() {
|
||||||
|
command.arg(flag);
|
||||||
|
}
|
||||||
|
command
|
||||||
|
.arg(task)
|
||||||
|
.current_dir(working_dir)
|
||||||
|
.stdin(Stdio::null());
|
||||||
|
command
|
||||||
|
}
|
||||||
|
|
||||||
fn normalize_task_for_harness(
|
fn normalize_task_for_harness(
|
||||||
harness: HarnessKind,
|
harness: HarnessKind,
|
||||||
task: &str,
|
task: &str,
|
||||||
profile: Option<&SessionAgentProfile>,
|
profile: Option<&SessionAgentProfile>,
|
||||||
|
) -> String {
|
||||||
|
let rendered = normalize_task_with_inline_system_prompt(task, profile);
|
||||||
|
|
||||||
|
match harness {
|
||||||
|
HarnessKind::Claude => task.to_string(),
|
||||||
|
HarnessKind::Codex | HarnessKind::OpenCode | HarnessKind::Gemini => rendered,
|
||||||
|
_ => task.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_task_with_inline_system_prompt(
|
||||||
|
task: &str,
|
||||||
|
profile: Option<&SessionAgentProfile>,
|
||||||
) -> String {
|
) -> String {
|
||||||
let Some(system_prompt) = profile.and_then(|profile| profile.append_system_prompt.as_ref())
|
let Some(system_prompt) = profile.and_then(|profile| profile.append_system_prompt.as_ref())
|
||||||
else {
|
else {
|
||||||
return task.to_string();
|
return task.to_string();
|
||||||
};
|
};
|
||||||
|
format!("System instructions:\n{system_prompt}\n\nTask:\n{task}")
|
||||||
match harness {
|
|
||||||
HarnessKind::Claude => task.to_string(),
|
|
||||||
HarnessKind::Codex | HarnessKind::OpenCode | HarnessKind::Gemini => {
|
|
||||||
format!("System instructions:\n{system_prompt}\n\nTask:\n{task}")
|
|
||||||
}
|
|
||||||
_ => task.to_string(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn spawn_claude_code(
|
async fn spawn_claude_code(
|
||||||
@@ -2796,8 +2917,15 @@ async fn spawn_claude_code(
|
|||||||
session_id: &str,
|
session_id: &str,
|
||||||
working_dir: &Path,
|
working_dir: &Path,
|
||||||
) -> Result<u32> {
|
) -> Result<u32> {
|
||||||
let mut command =
|
let mut command = build_agent_command(
|
||||||
build_agent_command("claude", agent_program, task, session_id, working_dir, None);
|
&Config::default(),
|
||||||
|
"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())
|
||||||
@@ -3490,6 +3618,7 @@ mod tests {
|
|||||||
auto_terminate_stale_sessions: false,
|
auto_terminate_stale_sessions: false,
|
||||||
default_agent: "claude".to_string(),
|
default_agent: "claude".to_string(),
|
||||||
default_agent_profile: None,
|
default_agent_profile: None,
|
||||||
|
harness_runners: Default::default(),
|
||||||
agent_profiles: Default::default(),
|
agent_profiles: Default::default(),
|
||||||
orchestration_templates: Default::default(),
|
orchestration_templates: Default::default(),
|
||||||
memory_connectors: Default::default(),
|
memory_connectors: Default::default(),
|
||||||
@@ -3534,6 +3663,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn build_agent_command_applies_profile_runner_flags_for_claude() {
|
fn build_agent_command_applies_profile_runner_flags_for_claude() {
|
||||||
|
let cfg = Config::default();
|
||||||
let profile = SessionAgentProfile {
|
let profile = SessionAgentProfile {
|
||||||
profile_name: "reviewer".to_string(),
|
profile_name: "reviewer".to_string(),
|
||||||
agent: None,
|
agent: None,
|
||||||
@@ -3548,6 +3678,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let command = build_agent_command(
|
let command = build_agent_command(
|
||||||
|
&cfg,
|
||||||
"claude",
|
"claude",
|
||||||
Path::new("claude"),
|
Path::new("claude"),
|
||||||
"review this change",
|
"review this change",
|
||||||
@@ -3590,6 +3721,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn build_agent_command_normalizes_runner_flags_for_codex() {
|
fn build_agent_command_normalizes_runner_flags_for_codex() {
|
||||||
|
let cfg = Config::default();
|
||||||
let profile = SessionAgentProfile {
|
let profile = SessionAgentProfile {
|
||||||
profile_name: "reviewer".to_string(),
|
profile_name: "reviewer".to_string(),
|
||||||
agent: None,
|
agent: None,
|
||||||
@@ -3604,6 +3736,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let command = build_agent_command(
|
let command = build_agent_command(
|
||||||
|
&cfg,
|
||||||
"codex",
|
"codex",
|
||||||
Path::new("codex"),
|
Path::new("codex"),
|
||||||
"review this change",
|
"review this change",
|
||||||
@@ -3641,6 +3774,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn build_agent_command_normalizes_runner_flags_for_opencode() {
|
fn build_agent_command_normalizes_runner_flags_for_opencode() {
|
||||||
|
let cfg = Config::default();
|
||||||
let profile = SessionAgentProfile {
|
let profile = SessionAgentProfile {
|
||||||
profile_name: "builder".to_string(),
|
profile_name: "builder".to_string(),
|
||||||
agent: None,
|
agent: None,
|
||||||
@@ -3655,6 +3789,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let command = build_agent_command(
|
let command = build_agent_command(
|
||||||
|
&cfg,
|
||||||
"opencode",
|
"opencode",
|
||||||
Path::new("opencode"),
|
Path::new("opencode"),
|
||||||
"stabilize callback flow",
|
"stabilize callback flow",
|
||||||
@@ -3685,6 +3820,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn build_agent_command_normalizes_runner_flags_for_gemini() {
|
fn build_agent_command_normalizes_runner_flags_for_gemini() {
|
||||||
|
let cfg = Config::default();
|
||||||
let profile = SessionAgentProfile {
|
let profile = SessionAgentProfile {
|
||||||
profile_name: "investigator".to_string(),
|
profile_name: "investigator".to_string(),
|
||||||
agent: None,
|
agent: None,
|
||||||
@@ -3699,6 +3835,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let command = build_agent_command(
|
let command = build_agent_command(
|
||||||
|
&cfg,
|
||||||
"gemini",
|
"gemini",
|
||||||
Path::new("gemini"),
|
Path::new("gemini"),
|
||||||
"investigate auth regression",
|
"investigate auth regression",
|
||||||
@@ -3725,6 +3862,111 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn agent_program_uses_configured_runner_for_cursor() -> Result<()> {
|
||||||
|
let mut cfg = Config::default();
|
||||||
|
cfg.harness_runners.insert(
|
||||||
|
"cursor".to_string(),
|
||||||
|
crate::config::HarnessRunnerConfig {
|
||||||
|
program: "cursor-agent".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
agent_program(&cfg, "cursor")?,
|
||||||
|
PathBuf::from("cursor-agent")
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn build_agent_command_uses_configured_runner_for_cursor() {
|
||||||
|
let mut cfg = Config::default();
|
||||||
|
cfg.harness_runners.insert(
|
||||||
|
"cursor".to_string(),
|
||||||
|
crate::config::HarnessRunnerConfig {
|
||||||
|
program: "cursor-agent".to_string(),
|
||||||
|
base_args: vec!["run".to_string()],
|
||||||
|
cwd_flag: Some("--cwd".to_string()),
|
||||||
|
session_name_flag: Some("--name".to_string()),
|
||||||
|
task_flag: Some("--task".to_string()),
|
||||||
|
model_flag: Some("--model".to_string()),
|
||||||
|
permission_mode_flag: Some("--permission-mode".to_string()),
|
||||||
|
add_dir_flag: Some("--context-dir".to_string()),
|
||||||
|
inline_system_prompt_for_task: true,
|
||||||
|
env: BTreeMap::from([("ECC_HARNESS".to_string(), "cursor".to_string())]),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let profile = SessionAgentProfile {
|
||||||
|
profile_name: "worker".to_string(),
|
||||||
|
agent: None,
|
||||||
|
model: Some("gpt-5.4".to_string()),
|
||||||
|
allowed_tools: Vec::new(),
|
||||||
|
disallowed_tools: Vec::new(),
|
||||||
|
permission_mode: Some("plan".to_string()),
|
||||||
|
add_dirs: vec![PathBuf::from("docs"), PathBuf::from("specs")],
|
||||||
|
max_budget_usd: None,
|
||||||
|
token_budget: None,
|
||||||
|
append_system_prompt: Some("Use repo context carefully.".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let command = build_agent_command(
|
||||||
|
&cfg,
|
||||||
|
"cursor",
|
||||||
|
Path::new("cursor-agent"),
|
||||||
|
"fix callback regression",
|
||||||
|
"sess-cur1",
|
||||||
|
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",
|
||||||
|
"--cwd",
|
||||||
|
"/tmp/repo",
|
||||||
|
"--name",
|
||||||
|
"ecc-sess-cur1",
|
||||||
|
"--model",
|
||||||
|
"gpt-5.4",
|
||||||
|
"--context-dir",
|
||||||
|
"docs",
|
||||||
|
"--context-dir",
|
||||||
|
"specs",
|
||||||
|
"--permission-mode",
|
||||||
|
"plan",
|
||||||
|
"--task",
|
||||||
|
"System instructions:\nUse repo context carefully.\n\nTask:\nfix callback regression",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
let mut envs = command
|
||||||
|
.as_std()
|
||||||
|
.get_envs()
|
||||||
|
.map(|(key, value)| {
|
||||||
|
(
|
||||||
|
key.to_string_lossy().to_string(),
|
||||||
|
value.map(|value| value.to_string_lossy().to_string()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
envs.sort();
|
||||||
|
assert_eq!(
|
||||||
|
envs,
|
||||||
|
vec![
|
||||||
|
("ECC_HARNESS".to_string(), Some("cursor".to_string())),
|
||||||
|
("ECC_SESSION_ID".to_string(), Some("sess-cur1".to_string())),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn build_session_record_canonicalizes_known_agent_aliases() -> Result<()> {
|
fn build_session_record_canonicalizes_known_agent_aliases() -> Result<()> {
|
||||||
let tempdir = TestDir::new("manager-canonical-agent-type")?;
|
let tempdir = TestDir::new("manager-canonical-agent-type")?;
|
||||||
|
|||||||
@@ -14590,6 +14590,7 @@ diff --git a/src/lib.rs b/src/lib.rs
|
|||||||
auto_terminate_stale_sessions: false,
|
auto_terminate_stale_sessions: false,
|
||||||
default_agent: "claude".to_string(),
|
default_agent: "claude".to_string(),
|
||||||
default_agent_profile: None,
|
default_agent_profile: None,
|
||||||
|
harness_runners: Default::default(),
|
||||||
agent_profiles: Default::default(),
|
agent_profiles: Default::default(),
|
||||||
orchestration_templates: Default::default(),
|
orchestration_templates: Default::default(),
|
||||||
memory_connectors: Default::default(),
|
memory_connectors: Default::default(),
|
||||||
|
|||||||
Reference in New Issue
Block a user