feat: canonicalize ecc2 harness aliases

This commit is contained in:
Affaan Mustafa
2026-04-10 08:03:25 -07:00
parent a4aaa30e93
commit d84c64fa0e
3 changed files with 126 additions and 10 deletions

View File

@@ -2103,11 +2103,12 @@ async fn queue_session_in_dir_with_runner_program(
grouping: SessionGrouping,
) -> Result<String> {
let profile = resolve_launch_profile(db, cfg, profile_name, inherited_profile_session_id)?;
let canonical_agent_type = HarnessKind::canonical_agent_type(agent_type);
queue_session_with_resolved_profile_and_runner_program(
db,
cfg,
task,
agent_type,
&canonical_agent_type,
use_worktree,
repo_root,
runner_program,
@@ -2132,10 +2133,11 @@ async fn queue_session_with_resolved_profile_and_runner_program(
.as_ref()
.and_then(|profile| profile.agent.as_deref())
.unwrap_or(agent_type);
let effective_agent_type = HarnessKind::canonical_agent_type(effective_agent_type);
let session = build_session_record(
db,
task,
effective_agent_type,
&effective_agent_type,
use_worktree,
cfg,
repo_root,
@@ -2188,6 +2190,7 @@ fn build_session_record(
repo_root: &Path,
grouping: SessionGrouping,
) -> Result<Session> {
let canonical_agent_type = HarnessKind::canonical_agent_type(agent_type);
let id = uuid::Uuid::new_v4().to_string()[..8].to_string();
let now = chrono::Utc::now();
@@ -2216,7 +2219,7 @@ fn build_session_record(
task: task.to_string(),
project,
task_group,
agent_type: agent_type.to_string(),
agent_type: canonical_agent_type,
working_dir,
state: SessionState::Pending,
pid: None,
@@ -2341,13 +2344,18 @@ fn direct_delegate_sessions(
lead_id: &str,
agent_type: &str,
) -> Result<Vec<Session>> {
let target_harness = HarnessKind::from_agent_type(agent_type);
let mut sessions = Vec::new();
for child_id in db.delegated_children(lead_id, 50)? {
let Some(session) = db.get_session(&child_id)? else {
continue;
};
if session.agent_type != agent_type {
if target_harness != HarnessKind::Unknown {
if HarnessKind::from_agent_type(&session.agent_type) != target_harness {
continue;
}
} else if session.agent_type != HarnessKind::canonical_agent_type(agent_type) {
continue;
}
@@ -3575,6 +3583,81 @@ mod tests {
);
}
#[test]
fn build_session_record_canonicalizes_known_agent_aliases() -> Result<()> {
let tempdir = TestDir::new("manager-canonical-agent-type")?;
let repo_root = tempdir.path().join("repo");
init_git_repo(&repo_root)?;
let cfg = build_config(tempdir.path());
let db = StateStore::open(&cfg.db_path)?;
let session = build_session_record(
&db,
"Investigate auth callback",
"gemini-cli",
false,
&cfg,
&repo_root,
SessionGrouping::default(),
)?;
assert_eq!(session.agent_type, "gemini");
Ok(())
}
#[test]
fn direct_delegate_sessions_matches_harness_aliases_for_existing_rows() -> Result<()> {
let tempdir = TestDir::new("manager-delegate-alias-match")?;
let repo_root = tempdir.path().join("repo");
init_git_repo(&repo_root)?;
let cfg = build_config(tempdir.path());
let db = StateStore::open(&cfg.db_path)?;
let now = Utc::now();
db.insert_session(&Session {
id: "lead".to_string(),
task: "Lead task".to_string(),
project: "workspace".to_string(),
task_group: "general".to_string(),
agent_type: "claude".to_string(),
working_dir: repo_root.clone(),
state: SessionState::Running,
pid: Some(42),
worktree: None,
created_at: now,
updated_at: now,
last_heartbeat_at: now,
metrics: SessionMetrics::default(),
})?;
db.insert_session(&Session {
id: "child".to_string(),
task: "Delegate task".to_string(),
project: "workspace".to_string(),
task_group: "general".to_string(),
agent_type: "claude-code".to_string(),
working_dir: repo_root.clone(),
state: SessionState::Idle,
pid: Some(7),
worktree: None,
created_at: now,
updated_at: now,
last_heartbeat_at: now,
metrics: SessionMetrics::default(),
})?;
db.send_message(
"lead",
"child",
"{\"task\":\"Delegate task\",\"context\":\"Delegated from lead\"}",
"task_handoff",
)?;
let delegates = direct_delegate_sessions(&db, "lead", "claude")?;
assert_eq!(delegates.len(), 1);
assert_eq!(delegates[0].id, "child");
Ok(())
}
#[test]
fn enforce_session_heartbeats_marks_overdue_running_sessions_stale() -> Result<()> {
let tempdir = TestDir::new("manager-heartbeat-stale")?;

View File

@@ -79,6 +79,13 @@ impl HarnessKind {
}
}
pub fn canonical_agent_type(agent_type: &str) -> String {
match Self::from_agent_type(agent_type) {
Self::Unknown => agent_type.trim().to_ascii_lowercase(),
harness => harness.as_str().to_string(),
}
}
fn project_markers(self) -> &'static [&'static str] {
match self {
Self::Claude => &[".claude"],
@@ -505,4 +512,18 @@ mod tests {
assert_eq!(harness.detected, vec![HarnessKind::Gemini]);
Ok(())
}
#[test]
fn canonical_agent_type_normalizes_known_aliases() {
assert_eq!(HarnessKind::canonical_agent_type("claude-code"), "claude");
assert_eq!(HarnessKind::canonical_agent_type("gemini-cli"), "gemini");
assert_eq!(
HarnessKind::canonical_agent_type("factory-droid"),
"factory_droid"
);
assert_eq!(
HarnessKind::canonical_agent_type(" custom-runner "),
"custom-runner"
);
}
}

View File

@@ -675,15 +675,23 @@ impl StateStore {
.collect::<std::result::Result<Vec<_>, _>>()?;
for (session_id, agent_type, working_dir) in updates {
let harness = SessionHarnessInfo::detect(&agent_type, Path::new(&working_dir));
let canonical_agent_type = HarnessKind::canonical_agent_type(&agent_type);
let harness =
SessionHarnessInfo::detect(&canonical_agent_type, Path::new(&working_dir));
let detected_json =
serde_json::to_string(&harness.detected).context("serialize detected harnesses")?;
self.conn.execute(
"UPDATE sessions
SET harness = ?2,
detected_harnesses_json = ?3
SET agent_type = ?2,
harness = ?3,
detected_harnesses_json = ?4
WHERE id = ?1",
rusqlite::params![session_id, harness.primary.to_string(), detected_json],
rusqlite::params![
session_id,
canonical_agent_type,
harness.primary.to_string(),
detected_json
],
)?;
}
@@ -3968,7 +3976,7 @@ mod tests {
"Backfill harness metadata",
"ecc",
"legacy",
"claude",
"gemini-cli",
repo_root.display().to_string(),
now,
],
@@ -3976,10 +3984,14 @@ mod tests {
drop(conn);
let db = StateStore::open(&db_path)?;
let session = db
.get_session("sess-legacy")?
.expect("legacy row should still exist");
assert_eq!(session.agent_type, "gemini");
let harness = db
.get_session_harness_info("sess-legacy")?
.expect("legacy row should be backfilled");
assert_eq!(harness.primary, HarnessKind::Claude);
assert_eq!(harness.primary, HarnessKind::Gemini);
assert_eq!(harness.detected, vec![HarnessKind::Codex]);
Ok(())
}