fix(windows): detect Cursor Agent Node wrapper (#8266)
This commit is contained in:
parent
c63ab965d8
commit
4c7bbed2fb
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Reference in New Issue