diff --git a/src/copilot/copilot-daemon.ts b/src/copilot/copilot-daemon.ts index 2dae17be..29116722 100644 --- a/src/copilot/copilot-daemon.ts +++ b/src/copilot/copilot-daemon.ts @@ -12,7 +12,7 @@ import * as http from 'http'; import { CopilotDaemonStatus } from './types'; import { CopilotConfig } from '../config/unified-config-types'; import { getCopilotDir, getCopilotApiBinPath } from './copilot-package-manager'; -import { verifyCopilotDaemonOwnership } from './daemon-process-ownership'; +import { verifyProcessOwnership } from '../cursor/daemon-process-ownership'; const DAEMON_HEALTH_MARKER = 'server running'; @@ -258,7 +258,11 @@ export async function stopDaemon(): Promise<{ success: boolean; error?: string } } try { - const ownership = verifyCopilotDaemonOwnership(pid); + const ownership = verifyProcessOwnership(pid, (commandLine) => { + const lower = commandLine.toLowerCase(); + // copilot-api is launched as `... copilot-api start --port ` + return lower.includes('copilot-api') && lower.includes(' start'); + }); if (ownership === 'not-running') { removePidFile(); return { success: true }; diff --git a/src/copilot/daemon-process-ownership.ts b/src/copilot/daemon-process-ownership.ts deleted file mode 100644 index 0d80f8d8..00000000 --- a/src/copilot/daemon-process-ownership.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { spawnSync } from 'child_process'; -import * as fs from 'fs'; - -export type DaemonOwnershipStatus = 'owned' | 'not-owned' | 'not-running' | 'unknown'; - -function getProcessCommandLine(pid: number): string | null { - if (process.platform === 'linux') { - try { - // /proc cmdline uses null separators between arguments. - return fs.readFileSync(`/proc/${pid}/cmdline`, 'utf8').replace(/\0/g, ' ').trim(); - } catch { - return null; - } - } - - if (process.platform === 'darwin') { - try { - const result = spawnSync('ps', ['-p', String(pid), '-o', 'command='], { - encoding: 'utf8', - }); - if (result.error || result.status !== 0) { - return null; - } - return result.stdout.trim(); - } catch { - return null; - } - } - - if (process.platform === 'win32') { - const command = `(Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}" | Select-Object -ExpandProperty CommandLine)`; - const shells = ['powershell.exe', 'powershell', 'pwsh.exe', 'pwsh']; - for (const shell of shells) { - try { - const result = spawnSync(shell, ['-NoProfile', '-Command', command], { - encoding: 'utf8', - }); - if (result.error) { - continue; - } - if (result.status !== 0) { - return null; - } - return result.stdout.trim(); - } catch { - // Try next shell candidate - } - } - return null; - } - - return null; -} - -export function verifyCopilotDaemonOwnership(pid: number): DaemonOwnershipStatus { - try { - process.kill(pid, 0); - } catch (err) { - const error = err as NodeJS.ErrnoException; - if (error.code === 'ESRCH') { - return 'not-running'; - } - return 'unknown'; - } - - const commandLine = getProcessCommandLine(pid); - if (!commandLine) { - return 'unknown'; - } - - // copilot-api is launched as `... copilot-api start --port ` - const lower = commandLine.toLowerCase(); - const looksLikeCopilotDaemon = lower.includes('copilot-api') && lower.includes(' start'); - - return looksLikeCopilotDaemon ? 'owned' : 'not-owned'; -} diff --git a/src/cursor/daemon-process-ownership.ts b/src/cursor/daemon-process-ownership.ts index c9c0eddf..81ac9384 100644 --- a/src/cursor/daemon-process-ownership.ts +++ b/src/cursor/daemon-process-ownership.ts @@ -52,7 +52,10 @@ function getProcessCommandLine(pid: number): string | null { return null; } -export function verifyDaemonOwnership(pid: number): DaemonOwnershipStatus { +export function verifyProcessOwnership( + pid: number, + ownershipMatcher: (commandLine: string) => boolean +): DaemonOwnershipStatus { try { process.kill(pid, 0); } catch (err) { @@ -68,8 +71,13 @@ export function verifyDaemonOwnership(pid: number): DaemonOwnershipStatus { return 'unknown'; } - const looksLikeCursorDaemon = - commandLine.includes('--ccs-daemon') && commandLine.includes('cursor-daemon-entry'); + return ownershipMatcher(commandLine) ? 'owned' : 'not-owned'; +} - return looksLikeCursorDaemon ? 'owned' : 'not-owned'; +export function verifyDaemonOwnership(pid: number): DaemonOwnershipStatus { + return verifyProcessOwnership( + pid, + (commandLine) => + commandLine.includes('--ccs-daemon') && commandLine.includes('cursor-daemon-entry') + ); }