mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-11 12:03:31 +08:00
feat: detect custom ecc2 harness markers
This commit is contained in:
@@ -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"));
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user