feat: add ecc2 harness compatibility env

This commit is contained in:
Affaan Mustafa 2026-04-10 10:24:33 -07:00
parent b51792fe0e
commit 176efb7623

View File

@ -3018,6 +3018,7 @@ fn build_agent_command(
if let Some(runner) = cfg.harness_runner(&SessionHarnessInfo::runner_key(agent_type)) {
return build_configured_harness_command(
runner,
agent_type,
agent_program,
task,
session_id,
@ -3028,7 +3029,7 @@ fn build_agent_command(
let task = normalize_task_for_harness(harness, task, profile);
let mut command = Command::new(agent_program);
command.env("ECC_SESSION_ID", session_id);
apply_shared_harness_runtime_env(&mut command, agent_type, session_id, working_dir, profile);
match harness {
HarnessKind::Claude => {
command
@ -3125,6 +3126,7 @@ fn build_agent_command(
fn build_configured_harness_command(
runner: &crate::config::HarnessRunnerConfig,
agent_type: &str,
agent_program: &Path,
task: &str,
session_id: &str,
@ -3132,7 +3134,7 @@ fn build_configured_harness_command(
profile: Option<&SessionAgentProfile>,
) -> Command {
let mut command = Command::new(agent_program);
command.env("ECC_SESSION_ID", session_id);
apply_shared_harness_runtime_env(&mut command, agent_type, session_id, working_dir, profile);
for (key, value) in &runner.env {
if !value.trim().is_empty() {
command.env(key, value);
@ -3211,6 +3213,52 @@ fn build_configured_harness_command(
command
}
fn apply_shared_harness_runtime_env(
command: &mut Command,
agent_type: &str,
session_id: &str,
working_dir: &Path,
profile: Option<&SessionAgentProfile>,
) {
let harness_label = SessionHarnessInfo::runner_key(agent_type);
command.env("ECC_SESSION_ID", session_id);
command.env("ECC_HARNESS", &harness_label);
command.env("ECC_WORKING_DIR", working_dir);
command.env("ECC_PROJECT_DIR", working_dir);
command.env("CLAUDE_SESSION_ID", session_id);
command.env("CLAUDE_PROJECT_DIR", working_dir);
command.env("CLAUDE_CODE_ENTRYPOINT", "cli");
if let Some(model) = profile.and_then(|profile| profile.model.as_ref()) {
command.env("CLAUDE_MODEL", model);
}
if let Some(plugin_root) = resolve_ecc_plugin_root() {
command.env("ECC_PLUGIN_ROOT", &plugin_root);
command.env("CLAUDE_PLUGIN_ROOT", &plugin_root);
}
}
fn resolve_ecc_plugin_root() -> Option<PathBuf> {
let mut seeds = Vec::new();
if let Ok(current_exe) = std::env::current_exe() {
seeds.push(current_exe);
}
seeds.push(PathBuf::from(env!("CARGO_MANIFEST_DIR")));
for seed in seeds {
for candidate in seed.ancestors() {
if is_ecc_plugin_root(candidate) {
return Some(candidate.to_path_buf());
}
}
}
None
}
fn is_ecc_plugin_root(candidate: &Path) -> bool {
candidate.join("scripts/lib/utils.js").is_file() && candidate.join("hooks/hooks.json").is_file()
}
fn normalize_task_for_harness(
harness: HarnessKind,
task: &str,
@ -4246,6 +4294,24 @@ mod tests {
"System instructions:\nReview thoroughly.\n\nECC execution profile:\n- Allowed tools: Read\n- Disallowed tools: Bash\n- Permission mode: plan\n- Max budget USD: 1.25\n- Token budget: 750\n\nTask:\nreview this change",
]
);
let envs = command_env_map(&command);
assert_eq!(envs.get("ECC_SESSION_ID"), Some(&"sess-1234".to_string()));
assert_eq!(
envs.get("CLAUDE_SESSION_ID"),
Some(&"sess-1234".to_string())
);
assert_eq!(
envs.get("CLAUDE_PROJECT_DIR"),
Some(&"/tmp/repo".to_string())
);
assert_eq!(envs.get("CLAUDE_CODE_ENTRYPOINT"), Some(&"cli".to_string()));
assert_eq!(envs.get("ECC_HARNESS"), Some(&"codex".to_string()));
assert_eq!(envs.get("CLAUDE_MODEL"), Some(&"gpt-5.4".to_string()));
assert!(
envs.contains_key("CLAUDE_PLUGIN_ROOT"),
"shared compatibility env should expose the ECC plugin root"
);
}
#[test]
@ -4441,24 +4507,20 @@ mod tests {
"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();
let envs = command_env_map(&command);
assert_eq!(envs.get("ECC_SESSION_ID"), Some(&"sess-cur1".to_string()));
assert_eq!(
envs,
vec![
("ECC_HARNESS".to_string(), Some("cursor".to_string())),
("ECC_SESSION_ID".to_string(), Some("sess-cur1".to_string())),
]
envs.get("CLAUDE_SESSION_ID"),
Some(&"sess-cur1".to_string())
);
assert_eq!(
envs.get("CLAUDE_PROJECT_DIR"),
Some(&"/tmp/repo".to_string())
);
assert_eq!(envs.get("CLAUDE_CODE_ENTRYPOINT"), Some(&"cli".to_string()));
assert_eq!(envs.get("ECC_HARNESS"), Some(&"cursor".to_string()));
assert_eq!(envs.get("CLAUDE_MODEL"), Some(&"gpt-5.4".to_string()));
assert_eq!(envs.get("ECC_PLUGIN_ROOT"), envs.get("CLAUDE_PLUGIN_ROOT"));
}
#[test]
@ -4806,7 +4868,7 @@ mod tests {
let script_path = root.join("fake-claude.sh");
let log_path = root.join("fake-claude.log");
let script = format!(
"#!/usr/bin/env python3\nimport os\nimport pathlib\nimport signal\nimport sys\nimport time\n\nlog_path = pathlib.Path(r\"{}\")\nlog_path.write_text(os.getcwd() + \"\\n\", encoding=\"utf-8\")\nwith log_path.open(\"a\", encoding=\"utf-8\") as handle:\n handle.write(\" \".join(sys.argv[1:]) + \"\\n\")\n handle.write(\"ECC_SESSION_ID=\" + os.environ.get(\"ECC_SESSION_ID\", \"\") + \"\\n\")\n\ndef handle_term(signum, frame):\n raise SystemExit(0)\n\nsignal.signal(signal.SIGTERM, handle_term)\nwhile True:\n time.sleep(0.1)\n",
"#!/usr/bin/env python3\nimport os\nimport pathlib\nimport signal\nimport sys\nimport time\n\nlog_path = pathlib.Path(r\"{}\")\nlog_path.write_text(os.getcwd() + \"\\n\", encoding=\"utf-8\")\nwith log_path.open(\"a\", encoding=\"utf-8\") as handle:\n handle.write(\" \".join(sys.argv[1:]) + \"\\n\")\n handle.write(\"ECC_SESSION_ID=\" + os.environ.get(\"ECC_SESSION_ID\", \"\") + \"\\n\")\n handle.write(\"CLAUDE_SESSION_ID=\" + os.environ.get(\"CLAUDE_SESSION_ID\", \"\") + \"\\n\")\n handle.write(\"CLAUDE_PROJECT_DIR=\" + os.environ.get(\"CLAUDE_PROJECT_DIR\", \"\") + \"\\n\")\n handle.write(\"CLAUDE_CODE_ENTRYPOINT=\" + os.environ.get(\"CLAUDE_CODE_ENTRYPOINT\", \"\") + \"\\n\")\n handle.write(\"CLAUDE_PLUGIN_ROOT=\" + os.environ.get(\"CLAUDE_PLUGIN_ROOT\", \"\") + \"\\n\")\n handle.write(\"ECC_HARNESS=\" + os.environ.get(\"ECC_HARNESS\", \"\") + \"\\n\")\n\ndef handle_term(signum, frame):\n raise SystemExit(0)\n\nsignal.signal(signal.SIGTERM, handle_term)\nwhile True:\n time.sleep(0.1)\n",
log_path.display()
);
@ -4834,6 +4896,21 @@ mod tests {
anyhow::bail!("timed out waiting for {}", path.display());
}
fn command_env_map(command: &Command) -> BTreeMap<String, String> {
command
.as_std()
.get_envs()
.filter_map(|(key, value)| {
value.map(|value| {
(
key.to_string_lossy().to_string(),
value.to_string_lossy().to_string(),
)
})
})
.collect()
}
#[tokio::test(flavor = "current_thread")]
async fn create_session_spawns_process_and_marks_session_running() -> Result<()> {
let tempdir = TestDir::new("manager-create-session")?;
@ -4869,6 +4946,13 @@ mod tests {
assert!(log.contains("--print"));
assert!(log.contains("implement lifecycle"));
assert!(log.contains(&format!("ECC_SESSION_ID={session_id}")));
assert!(log.contains(&format!("CLAUDE_SESSION_ID={session_id}")));
assert!(log.contains(&format!(
"CLAUDE_PROJECT_DIR={}",
repo_root.to_string_lossy()
)));
assert!(log.contains("CLAUDE_CODE_ENTRYPOINT=cli"));
assert!(log.contains("ECC_HARNESS=claude"));
stop_session_with_options(&db, &session_id, false).await?;
Ok(())