aboutsummaryrefslogtreecommitdiff
path: root/src/detect.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-12 14:12:18 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-12 14:12:49 +0300
commitaf4db0f7699e6fa46ea8ba6dc414fe3d6395c5bb (patch)
treea6ef61cffbae36a61dbf34cf9b5492fb72fd8796 /src/detect.rs
parent8f747e96ebe079ce60cadcd6931e195295b0ca1c (diff)
fix: detect nix-wrapped agent entrypoints
Diffstat (limited to 'src/detect.rs')
-rw-r--r--src/detect.rs91
1 files changed, 79 insertions, 12 deletions
diff --git a/src/detect.rs b/src/detect.rs
index ce06211c..2bd1fea7 100644
--- a/src/detect.rs
+++ b/src/detect.rs
@@ -607,29 +607,47 @@ fn normalized_process_name(process: &crate::platform::ForegroundProcess) -> Stri
}
}
+ if identify_agent(effective).is_some() {
+ return effective.to_string();
+ }
+
+ if let Some(wrapped_agent) =
+ cmdline_argv0_agent_name(process.cmdline.as_deref().unwrap_or_default())
+ {
+ return wrapped_agent;
+ }
+
effective.to_string()
}
fn wrapped_agent_name_from_cmdline(cmdline: &str) -> Option<String> {
for token in cmdline.split_whitespace() {
- let trimmed = token.trim_matches(|c| matches!(c, '"' | '\''));
- if trimmed.is_empty() || trimmed.starts_with('-') {
- continue;
+ if let Some(agent_name) = agent_name_from_path_token(token) {
+ return Some(agent_name);
}
-
- let basename = std::path::Path::new(trimmed)
- .file_name()
- .and_then(|name| name.to_str())
- .unwrap_or(trimmed);
- let Some(agent) = parse_agent_label(basename) else {
- continue;
- };
- return Some(agent_label(agent).to_string());
}
None
}
+fn cmdline_argv0_agent_name(cmdline: &str) -> Option<String> {
+ agent_name_from_path_token(cmdline.split_whitespace().next()?)
+}
+
+fn agent_name_from_path_token(token: &str) -> Option<String> {
+ let trimmed = token.trim_matches(|c| matches!(c, '"' | '\''));
+ if trimmed.is_empty() || trimmed.starts_with('-') {
+ return None;
+ }
+
+ let basename = std::path::Path::new(trimmed)
+ .file_name()
+ .and_then(|name| name.to_str())
+ .unwrap_or(trimmed);
+ let agent = parse_agent_label(basename)?;
+ Some(agent_label(agent).to_string())
+}
+
fn process_priority(process: &crate::platform::ForegroundProcess, normalized_name: &str) -> u8 {
let lower_name = normalized_name.to_lowercase();
if lower_name != process.name.to_lowercase() {
@@ -733,6 +751,42 @@ mod tests {
}
#[test]
+ fn identify_agent_in_job_detects_nix_wrapped_codex_from_cmdline_argv0() {
+ let job = crate::platform::ForegroundJob {
+ process_group_id: 123,
+ processes: vec![crate::platform::ForegroundProcess {
+ pid: 1,
+ name: ".codex-wrapped".to_string(),
+ argv0: None,
+ cmdline: Some("/etc/profiles/per-user/user/bin/codex --model gpt-5".to_string()),
+ }],
+ };
+
+ assert_eq!(
+ identify_agent_in_job(&job),
+ Some((Agent::Codex, "codex".to_string()))
+ );
+ }
+
+ #[test]
+ fn identify_agent_in_job_canonicalizes_nix_wrapped_aliases_from_cmdline_argv0() {
+ let job = crate::platform::ForegroundJob {
+ process_group_id: 123,
+ processes: vec![crate::platform::ForegroundProcess {
+ pid: 1,
+ name: ".claude-code-wrapped".to_string(),
+ argv0: None,
+ cmdline: Some("/nix/store/example/bin/claude-code".to_string()),
+ }],
+ };
+
+ assert_eq!(
+ identify_agent_in_job(&job),
+ Some((Agent::Claude, "claude".to_string()))
+ );
+ }
+
+ #[test]
fn identify_agent_in_job_detects_shell_wrapped_pi() {
let job = crate::platform::ForegroundJob {
process_group_id: 123,
@@ -755,6 +809,19 @@ mod tests {
assert_eq!(wrapped_agent_name_from_cmdline("bash -lc"), None);
}
+ #[test]
+ fn cmdline_argv0_agent_name_canonicalizes_known_aliases() {
+ assert_eq!(
+ cmdline_argv0_agent_name("/nix/store/example/bin/ghcs"),
+ Some("copilot".to_string())
+ );
+ }
+
+ #[test]
+ fn cmdline_argv0_agent_name_requires_exact_agent_basename() {
+ assert_eq!(cmdline_argv0_agent_name("/tmp/my-codex-helper"), None);
+ }
+
// ---- Workspace state rollup ----
// ---- No agent → Unknown ----