Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 63 additions & 1 deletion src/detect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -604,9 +604,28 @@ fn agent_name_from_path_token(token: &str) -> Option<String> {
}

fn agent_name_from_known_package_path(path: &str) -> Option<String> {
let components: Vec<String> = path
let raw_components: Vec<&str> = path
.split(['/', '\\'])
.filter(|component| !component.is_empty())
.collect();
let kimi_entrypoint = [

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@akbash-bot why do we need to add exact kimi paths? how do we solve this for other agents? this feels weird

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PowerShell exposes this launch as generic node.exe, so the argv script is the only process-identity signal Herdr receives. The exact match avoids treating unrelated Node programs as Kimi. This follows existing special cases for Pi, Qwen, MastraCode, and Cursor; it is not a scalable Kimi-specific design.

I agree the current shape is awkward. The general fix is a table-driven process-signature registry per canonical agent: executable aliases plus exact generic-runtime argv/package signatures. Platform code would only collect name/argv, and the shared detector would evaluate that registry for every agent. I would not put process identity in remotely updated screen manifests because identity gates lifecycle authority. Hooks also cannot bootstrap this today because known-agent reports require existing process ownership. This PR is the smallest restoration; replacing the existing special cases with a registry would be a broader refactor.

"node_modules",
"@moonshot-ai",
"kimi-code",
"dist",
"main.mjs",
];
if raw_components.len() >= kimi_entrypoint.len()
&& raw_components[raw_components.len() - kimi_entrypoint.len()..]
.iter()
.zip(kimi_entrypoint)
.all(|(actual, expected)| actual.eq_ignore_ascii_case(expected))
{
return Some(agent_label(Agent::Kimi).to_string());
}

let components: Vec<String> = raw_components
.into_iter()
.map(normalized_agent_lookup_name)
.collect();

Expand Down Expand Up @@ -968,6 +987,49 @@ mod tests {
}
}

#[test]
fn identify_agent_in_job_detects_node_wrapped_kimi_on_windows() {
let mut process = foreground_process(
123,
"node.exe",
&[
r"C:\Program Files\nodejs\node.exe",
r"C:\Users\user\AppData\Roaming\npm\node_modules\@moonshot-ai\kimi-code\dist\main.mjs",
],
);
process.argv0 = Some(r"C:\Program Files\nodejs\node.exe".to_string());
let job = crate::platform::ForegroundJob {
process_group_id: 123,
processes: vec![process],
};

assert_eq!(
identify_agent_in_job(&job),
Some((Agent::Kimi, "kimi".to_string()))
);
}

#[test]
fn identify_agent_in_job_ignores_node_wrapped_kimi_near_misses() {
for script in [
r"C:\Users\user\AppData\Roaming\npm\node_modules\@moonshot-ai\kimi-code\scripts\main.mjs",
r"C:\Users\user\AppData\Roaming\npm\node_modules\@moonshot-ai\kimi-code-helper\dist\main.mjs",
r"C:\Users\user\AppData\Roaming\npm\node_modules\@moonshot-ai\kimi-code.js\dist\main.mjs",
r"C:\Users\user\AppData\Roaming\npm\node_modules\@moonshot-ai\kimi-code\dist\main.mjs\helper.js",
] {
let job = crate::platform::ForegroundJob {
process_group_id: 123,
processes: vec![foreground_process(
123,
"node.exe",
&[r"C:\Program Files\nodejs\node.exe", script],
)],
};

assert_eq!(identify_agent_in_job(&job), None, "script: {script}");
}
}

#[test]
fn identify_agent_in_job_detects_windows_cursor_install() {
let job = crate::platform::ForegroundJob {
Expand Down
Loading