feat: detect custom ecc2 harness markers

This commit is contained in:
Affaan Mustafa
2026-04-10 09:08:06 -07:00
parent 4a1f3cbd3f
commit bbed46d3eb
5 changed files with 176 additions and 23 deletions

View File

@@ -84,6 +84,7 @@ pub struct ResolvedAgentProfile {
pub struct HarnessRunnerConfig {
pub program: String,
pub base_args: Vec<String>,
pub project_markers: Vec<PathBuf>,
pub cwd_flag: Option<String>,
pub session_name_flag: Option<String>,
pub task_flag: Option<String>,
@@ -752,6 +753,7 @@ impl Default for HarnessRunnerConfig {
Self {
program: String::new(),
base_args: Vec::new(),
project_markers: Vec::new(),
cwd_flag: None,
session_name_flag: None,
task_flag: None,
@@ -1266,6 +1268,7 @@ inherits = "a"
[harness_runners.cursor]
program = "cursor-agent"
base_args = ["run"]
project_markers = [".cursor", ".cursor/rules"]
cwd_flag = "--cwd"
session_name_flag = "--name"
task_flag = "--task"
@@ -1282,6 +1285,10 @@ ECC_HARNESS = "cursor"
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.project_markers,
vec![PathBuf::from(".cursor"), PathBuf::from(".cursor/rules")]
);
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"));

View File

@@ -1230,15 +1230,19 @@ async fn main() -> Result<()> {
for s in sessions {
let harness = harnesses
.get(&s.id)
.map(|info| info.primary_label.clone())
.unwrap_or_else(|| session::SessionHarnessInfo::runner_key(&s.agent_type));
.cloned()
.unwrap_or_else(|| {
session::SessionHarnessInfo::detect(&s.agent_type, &s.working_dir)
})
.with_config_detection(&cfg, &s.working_dir)
.primary_label;
println!("{} [{}] [{}] {}", s.id, s.state, harness, s.task);
}
}
Some(Commands::Status { session_id }) => {
sync_runtime_session_metrics(&db, &cfg)?;
let id = session_id.unwrap_or_else(|| "latest".to_string());
let status = session::manager::get_status(&db, &id)?;
let status = session::manager::get_status(&db, &cfg, &id)?;
println!("{status}");
}
Some(Commands::Team { session_id, depth }) => {

View File

@@ -158,7 +158,7 @@ pub fn list_sessions(db: &StateStore) -> Result<Vec<Session>> {
db.list_sessions()
}
pub fn get_status(db: &StateStore, id: &str) -> Result<SessionStatus> {
pub fn get_status(db: &StateStore, cfg: &Config, id: &str) -> Result<SessionStatus> {
let session = resolve_session(db, id)?;
let session_id = session.id.clone();
Ok(SessionStatus {
@@ -166,7 +166,8 @@ pub fn get_status(db: &StateStore, id: &str) -> Result<SessionStatus> {
.get_session_harness_info(&session_id)?
.unwrap_or_else(|| {
SessionHarnessInfo::detect(&session.agent_type, &session.working_dir)
}),
})
.with_config_detection(cfg, &session.working_dir),
profile: db.get_session_profile(&session_id)?,
session,
parent_session: db.latest_task_handoff_source(&session_id)?,
@@ -5500,12 +5501,38 @@ mod tests {
db.insert_session(&build_session("older", SessionState::Running, older))?;
db.insert_session(&build_session("newer", SessionState::Idle, newer))?;
let status = get_status(&db, "latest")?;
let status = get_status(&db, &cfg, "latest")?;
assert_eq!(status.session.id, "newer");
Ok(())
}
#[test]
fn get_status_uses_configured_custom_harness_markers() -> Result<()> {
let tempdir = TestDir::new("manager-custom-harness-status")?;
fs::create_dir_all(tempdir.path().join(".acme"))?;
let mut cfg = build_config(tempdir.path());
cfg.harness_runners.insert(
"acme-runner".to_string(),
crate::config::HarnessRunnerConfig {
project_markers: vec![PathBuf::from(".acme")],
..Default::default()
},
);
let db = StateStore::open(&cfg.db_path)?;
let mut session = build_session("custom", SessionState::Pending, Utc::now());
session.agent_type = "".to_string();
session.working_dir = tempdir.path().to_path_buf();
db.insert_session(&session)?;
let status = get_status(&db, &cfg, "custom")?;
assert_eq!(status.harness.primary, HarnessKind::Unknown);
assert_eq!(status.harness.primary_label, "acme-runner");
assert_eq!(status.harness.detected_summary(), "acme-runner");
Ok(())
}
#[test]
fn get_status_surfaces_handoff_lineage() -> Result<()> {
let tempdir = TestDir::new("manager-status-lineage")?;
@@ -5538,14 +5565,14 @@ mod tests {
"task_handoff",
)?;
let status = get_status(&db, "parent")?;
let status = get_status(&db, &cfg, "parent")?;
let rendered = status.to_string();
assert!(rendered.contains("Children:"));
assert!(rendered.contains("child"));
assert!(rendered.contains("sibling"));
let child_status = get_status(&db, "child")?;
let child_status = get_status(&db, &cfg, "child")?;
assert_eq!(child_status.parent_session.as_deref(), Some("parent"));
Ok(())

View File

@@ -111,9 +111,34 @@ pub struct SessionHarnessInfo {
pub primary: HarnessKind,
pub primary_label: String,
pub detected: Vec<HarnessKind>,
pub detected_labels: Vec<String>,
}
impl SessionHarnessInfo {
fn detected_labels_for(detected: &[HarnessKind]) -> Vec<String> {
detected.iter().map(|harness| harness.to_string()).collect()
}
fn configured_detected_labels(cfg: &crate::config::Config, working_dir: &Path) -> Vec<String> {
let mut labels = Vec::new();
for (name, runner) in &cfg.harness_runners {
if runner.project_markers.is_empty() {
continue;
}
if runner
.project_markers
.iter()
.any(|marker| working_dir.join(marker).exists())
{
let label = Self::runner_key(name);
if !label.is_empty() && !labels.contains(&label) {
labels.push(label);
}
}
}
labels
}
pub fn runner_key(agent_type: &str) -> String {
let canonical = HarnessKind::canonical_agent_type(agent_type);
match HarnessKind::from_agent_type(&canonical) {
@@ -167,10 +192,12 @@ impl SessionHarnessInfo {
harness => harness,
};
let detected_labels = Self::detected_labels_for(&detected);
Self {
primary,
primary_label: Self::primary_label_for(agent_type, primary),
detected,
detected_labels,
}
}
@@ -187,6 +214,7 @@ impl SessionHarnessInfo {
}
let normalized_label = harness_label.trim().to_ascii_lowercase();
let detected_labels = Self::detected_labels_for(&detected);
Self {
primary,
primary_label: if normalized_label.is_empty() {
@@ -195,18 +223,36 @@ impl SessionHarnessInfo {
normalized_label
},
detected,
detected_labels,
}
}
pub fn with_config_detection(
mut self,
cfg: &crate::config::Config,
working_dir: &Path,
) -> Self {
for label in Self::configured_detected_labels(cfg, working_dir) {
if !self.detected_labels.contains(&label) {
self.detected_labels.push(label);
}
}
if self.primary == HarnessKind::Unknown
&& self.primary_label == HarnessKind::Unknown.as_str()
&& !self.detected_labels.is_empty()
{
self.primary_label = self.detected_labels[0].clone();
}
self
}
pub fn detected_summary(&self) -> String {
if self.detected.is_empty() {
if self.detected_labels.is_empty() {
"none detected".to_string()
} else {
self.detected
.iter()
.map(|harness| harness.to_string())
.collect::<Vec<_>>()
.join(", ")
self.detected_labels.join(", ")
}
}
}
@@ -573,6 +619,7 @@ mod tests {
harness.detected,
vec![HarnessKind::Claude, HarnessKind::Codex]
);
assert_eq!(harness.detected_labels, vec!["claude", "codex"]);
assert_eq!(harness.detected_summary(), "claude, codex");
Ok(())
}
@@ -587,6 +634,7 @@ mod tests {
assert_eq!(harness.primary, HarnessKind::Gemini);
assert_eq!(harness.primary_label, "gemini");
assert_eq!(harness.detected, vec![HarnessKind::Gemini]);
assert_eq!(harness.detected_labels, vec!["gemini"]);
Ok(())
}
@@ -610,6 +658,7 @@ mod tests {
assert_eq!(harness.primary, HarnessKind::Unknown);
assert_eq!(harness.primary_label, "custom-runner");
assert!(harness.detected.is_empty());
assert!(harness.detected_labels.is_empty());
}
#[test]
@@ -626,6 +675,54 @@ mod tests {
harness.detected,
vec![HarnessKind::Claude, HarnessKind::Codex]
);
assert_eq!(harness.detected_labels, vec!["claude", "codex"]);
Ok(())
}
#[test]
fn config_detection_adds_custom_markers_to_detected_summary(
) -> Result<(), Box<dyn std::error::Error>> {
let repo = TestDir::new("session-harness-custom-config")?;
fs::create_dir_all(repo.path().join(".acme"))?;
let mut cfg = crate::config::Config::default();
cfg.harness_runners.insert(
"acme-runner".to_string(),
crate::config::HarnessRunnerConfig {
project_markers: vec![PathBuf::from(".acme")],
..Default::default()
},
);
let harness =
SessionHarnessInfo::detect("", repo.path()).with_config_detection(&cfg, repo.path());
assert_eq!(harness.primary, HarnessKind::Unknown);
assert_eq!(harness.primary_label, "acme-runner");
assert_eq!(harness.detected_labels, vec!["acme-runner"]);
assert_eq!(harness.detected_summary(), "acme-runner");
Ok(())
}
#[test]
fn config_detection_preserves_custom_primary_label_and_appends_marker_matches(
) -> Result<(), Box<dyn std::error::Error>> {
let repo = TestDir::new("session-harness-config-append")?;
fs::create_dir_all(repo.path().join(".acme"))?;
fs::create_dir_all(repo.path().join(".codex"))?;
let mut cfg = crate::config::Config::default();
cfg.harness_runners.insert(
"acme-runner".to_string(),
crate::config::HarnessRunnerConfig {
project_markers: vec![PathBuf::from(".acme")],
..Default::default()
},
);
let harness = SessionHarnessInfo::detect("acme-runner", repo.path())
.with_config_detection(&cfg, repo.path());
assert_eq!(harness.primary, HarnessKind::Unknown);
assert_eq!(harness.primary_label, "acme-runner");
assert_eq!(harness.detected_labels, vec!["codex", "acme-runner"]);
assert_eq!(harness.detected_summary(), "codex, acme-runner");
Ok(())
}

View File

@@ -476,6 +476,29 @@ impl SessionCompletionSummary {
}
}
fn load_session_harnesses(
db: &StateStore,
cfg: &Config,
sessions: &[Session],
) -> HashMap<String, SessionHarnessInfo> {
let working_dirs = sessions
.iter()
.map(|session| (session.id.as_str(), session.working_dir.as_path()))
.collect::<HashMap<_, _>>();
db.list_session_harnesses()
.unwrap_or_default()
.into_iter()
.map(|(session_id, info)| {
let info = if let Some(working_dir) = working_dirs.get(session_id.as_str()) {
info.with_config_detection(cfg, working_dir)
} else {
info
};
(session_id, info)
})
.collect()
}
impl Dashboard {
pub fn new(db: StateStore, cfg: Config) -> Self {
Self::with_output_store(db, cfg, SessionOutputStore::default())
@@ -498,7 +521,7 @@ impl Dashboard {
let _ = db.sync_tool_activity_metrics(&cfg.tool_activity_metrics_path());
}
let sessions = db.list_sessions().unwrap_or_default();
let session_harnesses = db.list_session_harnesses().unwrap_or_default();
let session_harnesses = load_session_harnesses(&db, &cfg, &sessions);
let initial_session_states = sessions
.iter()
.map(|session| (session.id.clone(), session.state.clone()))
@@ -4040,13 +4063,7 @@ impl Dashboard {
Vec::new()
}
};
self.session_harnesses = match self.db.list_session_harnesses() {
Ok(harnesses) => harnesses,
Err(error) => {
tracing::warn!("Failed to refresh session harnesses: {error}");
HashMap::new()
}
};
self.session_harnesses = load_session_harnesses(&self.db, &self.cfg, &self.sessions);
self.unread_message_counts = match self.db.unread_message_counts() {
Ok(counts) => counts,
Err(error) => {
@@ -14488,7 +14505,8 @@ diff --git a/src/lib.rs b/src/lib.rs
.map(|session| {
(
session.id.clone(),
SessionHarnessInfo::detect(&session.agent_type, &session.working_dir),
SessionHarnessInfo::detect(&session.agent_type, &session.working_dir)
.with_config_detection(&cfg, &session.working_dir),
)
})
.collect();