fix(windows): detect Cursor Agent Node wrapper (#8266)

This commit is contained in:
BingZ 2026-07-24 14:18:50 +08:00 committed by GitHub
parent c63ab965d8
commit 4c7bbed2fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 83 additions and 21 deletions

View File

@ -289,6 +289,55 @@ describe('resolveAgentForegroundProcess', () => {
await expect(resolveAgentForegroundProcess(100, 'powershell.exe')).resolves.toBe('codex')
})
it('recognizes the native Windows Cursor launcher process tree', async () => {
Object.defineProperty(process, 'platform', { value: 'win32' })
mockPs(
windowsProcessJsonRows([
{
CommandLine: 'powershell.exe',
Name: 'powershell.exe',
ParentProcessId: 99,
ProcessId: 100
},
{
CommandLine: 'cmd.exe /c cursor-agent.cmd',
Name: 'cmd.exe',
ParentProcessId: 100,
ProcessId: 101
},
{
CommandLine:
'powershell.exe -File C:\\Users\\dev\\AppData\\Local\\cursor-agent\\cursor-agent.ps1',
Name: 'powershell.exe',
ParentProcessId: 101,
ProcessId: 102
},
{
CommandLine:
'node.exe C:\\Users\\dev\\AppData\\Local\\cursor-agent\\versions\\2026.07.09-a3815c0\\index.js',
Name: 'node.exe',
ParentProcessId: 102,
ProcessId: 103
},
{
CommandLine:
'node.exe C:\\Users\\dev\\AppData\\Local\\cursor-agent\\versions\\2026.07.09-a3815c0\\index.js worker-server',
Name: 'node.exe',
ParentProcessId: 103,
ProcessId: 104
},
{
CommandLine: 'C:\\Users\\dev\\.grok\\bin\\agent.exe',
Name: 'agent.exe',
ParentProcessId: 100,
ProcessId: 105
}
])
)
await expect(resolveAgentForegroundProcess(100, 'powershell.exe')).resolves.toBe('cursor-agent')
})
it('recognizes Windows Git Bash shell-rooted agent launches', async () => {
Object.defineProperty(process, 'platform', { value: 'win32' })
execFileMock.mockImplementation(

View File

@ -212,6 +212,24 @@ describe('agent process recognition', () => {
expect(recognizeAgentProcessFromCommandLine('node /usr/local/bin/orca status')).toBeNull()
})
it('recognizes the versioned Cursor Node wrapper without accepting generic agent processes', () => {
const cursorEntrypoint = String.raw`C:\Users\dev\AppData\Local\cursor-agent\versions\2026.07.09-a3815c0\index.js`
expect(recognizeAgentProcessFromCommandLine(`node.exe ${cursorEntrypoint}`)).toEqual({
agent: 'cursor',
processName: 'cursor-agent'
})
expect(
recognizeAgentProcessFromCommandLine(`node.exe ${cursorEntrypoint} worker-server`)
).toEqual({ agent: 'cursor', processName: 'cursor-agent' })
expect(
recognizeAgentProcessFromCommandLine(String.raw`node.exe C:\repo\cursor-agent\index.js`)
).toBeNull()
expect(
recognizeAgentProcessFromCommandLine(String.raw`C:\Users\dev\.grok\bin\agent.exe`)
).toBeNull()
})
it('does not classify prompt text as a wrapped agent command', () => {
expect(
recognizeAgentProcessFromCommandLine(

View File

@ -52,6 +52,7 @@ const NODE_PACKAGE_SCRIPT_ENTRYPOINTS: Record<string, readonly string[]> = {
codex: ['node_modules/@openai/codex/'],
gemini: ['node_modules/@google/gemini-cli/']
}
const CURSOR_AGENT_NODE_ENTRYPOINT_RE = /(?:^|\/)cursor-agent\/versions\/[^/]+\/index\.js$/
const PYTHON_SCRIPT_ENTRYPOINT_DIRECTORIES = ['/bin/', '/scripts/', '/site-packages/']
const PROCESS_TO_AGENT = new Map<string, TuiAgent>()
@ -95,6 +96,11 @@ function agentForNormalizedProcess(normalized: string): TuiAgent | undefined {
return undefined
}
function recognizedAgentForProcess(normalized: string): RecognizedAgentProcess | null {
const agent = agentForNormalizedProcess(normalized)
return agent ? { agent, processName: normalized } : null
}
function tokenizeCommandLine(commandLine: string): string[] {
const tokens: string[] = []
let current = ''
@ -197,20 +203,21 @@ function comparablePath(token: string): string {
}
function recognizeNodeScriptEntrypoint(token: string): RecognizedAgentProcess | null {
const path = comparablePath(token)
// Why: Cursor's native Windows launcher runs a generic versioned index.js,
// so its install path is the only stable identity that avoids ordinary Node apps.
if (CURSOR_AGENT_NODE_ENTRYPOINT_RE.test(path)) {
return { agent: 'cursor', processName: 'cursor-agent' }
}
const normalized = normalizeProcessName(token, { stripInterpreterScriptExtension: true })
const markers = NODE_PACKAGE_SCRIPT_ENTRYPOINTS[normalized]
if (!markers) {
return null
}
const path = comparablePath(token)
if (!markers.some((marker) => path.includes(marker))) {
return null
}
const agent = agentForNormalizedProcess(normalized)
if (!agent) {
return null
}
return { agent, processName: normalized }
return recognizedAgentForProcess(normalized)
}
function recognizePythonModule(
@ -220,11 +227,7 @@ function recognizePythonModule(
return null
}
const normalized = moduleName.split('.', 1)[0]?.toLowerCase() ?? ''
const agent = agentForNormalizedProcess(normalized)
if (!agent) {
return null
}
return { agent, processName: normalized }
return recognizedAgentForProcess(normalized)
}
function recognizePythonScriptEntrypoint(token: string): RecognizedAgentProcess | null {
@ -237,11 +240,7 @@ function recognizePythonScriptEntrypoint(token: string): RecognizedAgentProcess
}
const basename = path.split('/').pop() ?? ''
const normalized = basename.replace(PYTHON_SCRIPT_EXTENSION_RE, '')
const agent = agentForNormalizedProcess(normalized)
if (!agent) {
return null
}
return { agent, processName: normalized }
return recognizedAgentForProcess(normalized)
}
function recognizePythonEntrypoint(
@ -274,11 +273,7 @@ export function recognizeAgentProcess(
processName: string | null | undefined
): RecognizedAgentProcess | null {
const normalized = normalizeProcessName(processName)
const agent = agentForNormalizedProcess(normalized)
if (!agent) {
return null
}
return { agent, processName: normalized }
return recognizedAgentForProcess(normalized)
}
export function recognizeAgentProcessFromCommandLine(