| |
| |
| |
| |
| |
|
|
| import { exec } from 'node:child_process'; |
| import { promisify } from 'node:util'; |
| import os from 'node:os'; |
| import path from 'node:path'; |
|
|
| const execAsync = promisify(exec); |
|
|
| const MAX_TRAVERSAL_DEPTH = 32; |
|
|
| interface ProcessInfo { |
| pid: number; |
| parentPid: number; |
| name: string; |
| command: string; |
| } |
|
|
| interface RawProcessInfo { |
| ProcessId?: number; |
| ParentProcessId?: number; |
| Name?: string; |
| CommandLine?: string; |
| } |
|
|
| |
| |
| |
| async function getProcessTableWindows(): Promise<Map<number, ProcessInfo>> { |
| const processMap = new Map<number, ProcessInfo>(); |
| try { |
| |
| const powershellCommand = |
| 'Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,Name,CommandLine | ConvertTo-Json -Compress'; |
| |
| const { stdout } = await execAsync( |
| 'powershell -NoProfile -NonInteractive -Command "' + |
| powershellCommand + |
| '"', |
| { |
| maxBuffer: 10 * 1024 * 1024, |
| timeout: 5000, |
| }, |
| ); |
|
|
| if (!stdout.trim()) { |
| return processMap; |
| } |
|
|
| let processes: RawProcessInfo | RawProcessInfo[]; |
| try { |
| |
| processes = JSON.parse(stdout); |
| } catch { |
| return processMap; |
| } |
|
|
| if (!Array.isArray(processes)) { |
| processes = [processes]; |
| } |
|
|
| for (const p of processes) { |
| if (p && typeof p.ProcessId === 'number') { |
| processMap.set(p.ProcessId, { |
| pid: p.ProcessId, |
| parentPid: p.ParentProcessId || 0, |
| name: p.Name || '', |
| command: p.CommandLine || '', |
| }); |
| } |
| } |
| } catch { |
| |
| } |
| return processMap; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async function getProcessInfo(pid: number): Promise<{ |
| parentPid: number; |
| name: string; |
| command: string; |
| }> { |
| try { |
| const command = `ps -o ppid=,command= -p ${pid}`; |
| const { stdout } = await execAsync(command, { timeout: 3000 }); |
| const trimmedStdout = stdout.trim(); |
| if (!trimmedStdout) { |
| return { parentPid: 0, name: '', command: '' }; |
| } |
| const parts = trimmedStdout.split(/\s+/); |
| const ppidString = parts[0]; |
| const parentPid = parseInt(ppidString, 10); |
| const fullCommand = trimmedStdout.substring(ppidString.length).trim(); |
| const processName = path.basename(fullCommand.split(' ')[0]); |
|
|
| return { |
| parentPid: isNaN(parentPid) ? 1 : parentPid, |
| name: processName, |
| command: fullCommand, |
| }; |
| } catch { |
| return { parentPid: 0, name: '', command: '' }; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function getIdeProcessInfoForUnix(): Promise<{ |
| pid: number; |
| command: string; |
| }> { |
| const shells = ['zsh', 'bash', 'sh', 'tcsh', 'csh', 'ksh', 'fish', 'dash']; |
| let currentPid = process.pid; |
|
|
| for (let i = 0; i < MAX_TRAVERSAL_DEPTH; i++) { |
| try { |
| const { parentPid, name } = await getProcessInfo(currentPid); |
|
|
| const isShell = shells.some((shell) => name === shell); |
| if (isShell) { |
| |
| |
| |
| let idePid = parentPid; |
| try { |
| const { parentPid: grandParentPid } = await getProcessInfo(parentPid); |
| if (grandParentPid > 1) { |
| idePid = grandParentPid; |
| } |
| } catch { |
| |
| } |
| const { command } = await getProcessInfo(idePid); |
| return { pid: idePid, command }; |
| } |
|
|
| if (parentPid <= 1) { |
| break; |
| } |
| currentPid = parentPid; |
| } catch { |
| |
| break; |
| } |
| } |
|
|
| const { command } = await getProcessInfo(currentPid); |
| return { pid: currentPid, command }; |
| } |
|
|
| |
| |
| |
| async function getIdeProcessInfoForWindows(): Promise<{ |
| pid: number; |
| command: string; |
| }> { |
| |
| const processMap = await getProcessTableWindows(); |
| const myPid = process.pid; |
| const myProc = processMap.get(myPid); |
|
|
| if (!myProc) { |
| |
| const { command } = await getProcessInfo(myPid); |
| return { pid: myPid, command }; |
| } |
|
|
| |
| |
| const ancestors: ProcessInfo[] = []; |
| let curr: ProcessInfo | undefined = myProc; |
|
|
| for (let i = 0; i < MAX_TRAVERSAL_DEPTH && curr; i++) { |
| ancestors.push(curr); |
| if (curr.parentPid === 0 || !processMap.has(curr.parentPid)) { |
| break; |
| } |
| curr = processMap.get(curr.parentPid); |
| } |
|
|
| if (ancestors.length >= 3) { |
| const target = ancestors[ancestors.length - 3]; |
| return { pid: target.pid, command: target.command }; |
| } else if (ancestors.length > 0) { |
| const target = ancestors[ancestors.length - 1]; |
| return { pid: target.pid, command: target.command }; |
| } |
|
|
| return { pid: myPid, command: myProc.command }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function getIdeProcessInfo(): Promise<{ |
| pid: number; |
| command: string; |
| }> { |
| const platform = os.platform(); |
|
|
| if (process.env['GEMINI_CLI_IDE_PID']) { |
| const idePid = parseInt(process.env['GEMINI_CLI_IDE_PID'], 10); |
| if (!isNaN(idePid) && idePid > 0) { |
| if (platform === 'win32') { |
| const processMap = await getProcessTableWindows(); |
| const proc = processMap.get(idePid); |
| return { pid: idePid, command: proc?.command || '' }; |
| } |
| const { command } = await getProcessInfo(idePid); |
| return { pid: idePid, command }; |
| } |
| } |
|
|
| if (platform === 'win32') { |
| return getIdeProcessInfoForWindows(); |
| } |
|
|
| return getIdeProcessInfoForUnix(); |
| } |
|
|